diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e717f5e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 84cef4f..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 5 - }, - "extends": "eslint:recommended", - "env": { - "commonjs": true - }, - "rules": { - "strict": [2, "global"], - "block-scoped-var": 2, - "consistent-return": 2, - "eqeqeq": [2, "smart"], - "guard-for-in": 2, - "no-caller": 2, - "no-extend-native": 2, - "no-loop-func": 2, - "no-new": 2, - "no-param-reassign": 2, - "no-return-assign": 2, - "no-unused-expressions": 2, - "no-use-before-define": 2, - "radix": [2, "always"], - "indent": [2, 2], - "quotes": [2, "double"], - "semi": [2, "always"] - } -} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..a9531f6 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contributing to URI + +Thanks for your interest in contributing to `uri`! We welcome new contributions regardless of your level of experience or familiarity with PureScript. + +Every library in the Contributors organization shares a simple handbook that helps new contributors get started. With that in mind, please [read the short contributing guide on purescript-contrib/governance](https://github.com/purescript-contrib/governance/blob/main/contributing.md) before contributing to this library. diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..b79b995 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,19 @@ +--- +name: Bug report +about: Report an issue +title: "" +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of the bug. + +**To Reproduce** +A minimal code example (preferably a runnable example on [Try PureScript](https://try.purescript.org)!) or steps to reproduce the issue. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/change-request.md b/.github/ISSUE_TEMPLATE/change-request.md new file mode 100644 index 0000000..a2ee685 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/change-request.md @@ -0,0 +1,21 @@ +--- +name: Change request +about: Propose an improvement to this library +title: "" +labels: "" +assignees: "" +--- + +**Is your change request related to a problem? Please describe.** +A clear and concise description of the problem. + +Examples: + +- It's frustrating to have to [...] +- I was looking for a function to [...] + +**Describe the solution you'd like** +A clear and concise description of what a good solution to you looks like, including any solutions you've already considered. + +**Additional context** +Add any other context about the change request here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c47a263 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: PureScript Discourse + url: https://discourse.purescript.org/ + about: Ask and answer questions here. + - name: Functional Programming Slack + url: https://functionalprogramming.slack.com + about: For casual chat and questions (use https://fpchat-invite.herokuapp.com to join). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d8780f7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +**Description of the change** +Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. + +--- + +**Checklist:** + +- [ ] Added the change to the changelog's "Unreleased" section with a link to this PR and your username +- [ ] Linked any existing issues or proposals that this pull request should close +- [ ] Updated or added relevant documentation in the README and/or documentation directory +- [ ] Added a test for the contribution (if applicable) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6b0550f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up a PureScript toolchain + uses: purescript-contrib/setup-purescript@main + + - name: Cache PureScript dependencies + uses: actions/cache@v2 + with: + key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }} + path: | + .spago + output + + - name: Install dependencies + run: spago install + + - name: Build source + run: spago build --no-install --purs-args '--censor-lib --strict' + + - name: Run tests + run: spago test --no-install diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..002dae8 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +name: "Stale" + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + env: + days-until-stale: 60 + days-until-close: 14 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "This issue is stale because it has been open for ${{ env.days-until-stale }} days with no activity. Remove the stale label or comment to keep this issue open. Otherwise, this issue will be closed in ${{ env.days-until-close }} days." + stale-pr-message: "This pull request is stale because it has been open for ${{ env.days-until-stale }} days with no activity. Remove the stale label or comment to keep this pull request open. Otherwise, this pull request will be closed in ${{ env.days-until-close }} days." + days-before-stale: ${{ env.days-until-stale }} + days-before-close: ${{ env.days-until-close }} + stale-issue-label: "stale" + stale-pr-label: "stale" + exempt-pr-labels: "breaking change" diff --git a/.gitignore b/.gitignore index 709fd09..7bca306 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ -/.* -!/.gitignore -!/.eslintrc.json -!/.travis.yml -package-lock.json -/bower_components/ -/node_modules/ -/output/ +.* +!.gitignore +!.github +!.editorconfig + +output +generated-docs +bower_components diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2e30c22..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: node_js -dist: trusty -sudo: required -node_js: stable -install: - - npm install -g bower - - npm install -script: - - bower install --production - - npm run -s build - - bower install - - npm -s test -after_success: -- >- - test $TRAVIS_TAG && - echo $GITHUB_TOKEN | pulp login && - echo y | pulp publish --no-push diff --git a/README.md b/README.md index 03147c2..c074fe2 100644 --- a/README.md +++ b/README.md @@ -1,148 +1,43 @@ -# purescript-uri +# URI -[![Latest release](http://img.shields.io/github/release/purescript-contrib/purescript-uri.svg)](https://github.com/purescript-contrib/purescript-uri/releases) -[![Build status](https://travis-ci.org/purescript-contrib/purescript-uri.svg?branch=master)](https://travis-ci.org/purescript-contrib/purescript-uri) +[![CI](https://github.com/purescript-contrib/purescript-uri/workflows/CI/badge.svg?branch=main)](https://github.com/purescript-contrib/purescript-uri/actions?query=workflow%3ACI+branch%3Amain) +[![Release](http://img.shields.io/github/release/purescript-contrib/purescript-uri.svg)](https://github.com/purescript-contrib/purescript-uri/releases) +[![Pursuit](http://pursuit.purescript.org/packages/purescript-uri/badge)](http://pursuit.purescript.org/packages/purescript-uri) +[![Maintainer: garyb](https://img.shields.io/badge/maintainer-garyb-teal.svg)](http://github.com/garyb) A type-safe parser, printer, and ADT for URLs and URIs based on [RFC 3986](http://tools.ietf.org/html/rfc3986). ## Installation -``` -bower install purescript-uri -``` - -## Getting started - -The types and names here are a fairly faithful representation of the components described in the spec. - -- [`URI`][URI] is for absolutely specified URIs that can also have path, query, and fragment (hash) parts. -- [`AbsoluteURI`][AbsoluteURI] is a variation on `URI` that drops the ability for the URI to carry a fragment. -- [`RelativeRef`][RelativeRef] is for relatively specified URIs that can also have path, query, and fragment (hash) parts. -- [`URIRef`][URIRef] is combination of `URI` and `RelativeRef`, allowing the full range of representable URIs. - -The absolute/relative terminology when applied to URIs does not relate to the paths that a URI may carry, it refers to whether the URI has a "scheme" or not. For example `http://example.com` and `file://../test.txt` are absolute URIs but `//example.com` and `/test.txt` are relative. - -Assuming none of the `unsafe`-prefixed functions are used when constructing a URI, it should be impossible to construct a URI that is invalid using the types this library provides*. The slight downside of this is the data structures are relatively complex so as to only admit correct possibilities. - -\* Actually, there is one exception to that - `IPv6Address` is far too forgiving in what it allows currently. Contributions welcome! - -### URI component representations - -Due to the differing needs of users of this library, the URI types are all parameterised to allow for custom representations to be used for parts of the URI. Take a look at the most heavily parametrised type, `URIRef`: - -``` purescript -type URIRef userInfo hosts path hierPath relPath query fragment = ... -``` - -This allows us to provide hooks into the parsing and printing processes for a URI, so that types better suited to the intended use case can be used. - -Taking `userInfo` as an example, according to the spec, the `user-info` part of an authority is just an arbitrary string of characters terminated by an `@` before a hostname. An extremely common usage for this is the `user:password` scheme, so by leaving the choice of representation as a type variable we can switch it out for a type specifically designed to handle that (this library includes one actually, under [`URI.Extra.UserPassInfo`][UserPassInfo]). - -### App-specific URI type definitions - -When using this library, you'll probably want to define type synonyms for the URIs that make sense for your use case. A URI type that uses the simple representations for each component will look something like this: - -``` purescript -type MyURI = URIRef UserInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment -``` - -Along with these types, you'll want to define an options record that specifies how to parse and print URIs that look like this: - -``` purescript -options ∷ Record (URIRefOptions UserInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment) -options = - { parseUserInfo: pure - , printUserInfo: identity - , parseHosts: HostPortPair.parser pure pure - , printHosts: HostPortPair.print identity identity - , parsePath: pure - , printPath: identity - , parseHierPath: pure - , printHierPath: identity - , parseRelPath: pure - , printRelPath: identity - , parseQuery: pure - , printQuery: identity - , parseFragment: pure - , printFragment: identity - } -``` - -As you can see by all the `pure` and `identity`, we're not doing a whole lot here. `parseHosts` is a bit of an exception, but that's just due to the way that case is handled (see [later in this README](#host-parsing) for more details about that). - -These types ([`UserInfo`][UserInfo], [`HostPortPair`][HostPortPair], [`Host`][Host], etc.) are all provided by the library, and where necessary can only be constructed via smart constructor. This ensures that percent-encoding is applied to characters where necessary to ensure the constructed values will print as valid URIs, and so on. - -If we decided that we wanted to support `user:password` style user-info, we'd modify this by changing our type to use [`UserPassInfo`][UserPassInfo]: - -``` purescript -type MyURI = URIRef UserPassInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment -``` - -And update our options to use the appropriate parse/print functions accordingly: +Install `uri` with [Spago](https://github.com/purescript/spago): -``` purescript -options ∷ Record (URIRefOptions UserPassInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment) -options = - { parseUserInfo: UserPassInfo.parse - , printUserInfo: UserPassInfo.print - , ... +```sh +spago install uri ``` -### Writing custom component types - -These `parse/print` functions all share much the same shape of signature. For the case in the previous example, they come out as: - -``` purescript -parseUserInfo ∷ UserInfo → Either URIPartParseError UserPassInfo -printUserInfo ∷ UserPassInfo → UserInfo -``` +## Quick start -So you can see that for each component, when the options hooks/custom representation stuff is used, we take one of these library-provided component types and parse it into our new representation, and also print it back to that simple type later. +The quick start hasn't been written yet (contributions are welcome!). The quick start covers a common, minimal use case for the library, whereas longer examples and tutorials are kept in the [docs directory](./docs.) -Each of the library-provided component types have a `toString` function that extracts the inner value as a string after applying percent-decoding, and an `unsafeToString` that provides exactly the value that was parsed, preserving percent decoding. Similarly, there's a `fromString` that performs the minimal amount of required percent encoding for that part of the URI, and an `unsafeFromString` that performs no encoding at all. +## Documentation -You may ask why it's ever useful to have access to the encoded values, or to be able to print without encoding, so here's a motivating example: +`uri` documentation is stored in a few places: -For the [`UserPassInfo`][UserPassInfo] example, the typical way of encoding a username or password that contains a colon within it is to use `%3A` (`us:er` becomes `us%3Aer`). This allows colons-within-the-values to be recongised as independent from the colon-separating-username-and-password (`us%3Aer:password`). - -According to the spec it is not a requirement to encode colons in this part of the URI scheme, so just using [`toString`][UserInfo.toString] on `us:er` will get us back a `us:er`, resulting in `us:er:password`, so we'd have no way of knowing where the user ends and where the password starts. - -The solution when printing is to do some custom encoding that also replaces `:` with `%3A` for the user/password parts, and then joins them with the unencoded `:` afterwards. If we constructed the resulting [`UserInfo`][UserInfo] value with [`fromString`][UserInfo.fromString] it would re-encode our already encoded user/password parts (giving us `%253A` instead of `%3A`), so we use [`unsafeFromString`][UserInfo.unsafeFromString] since we've done the encoding ourselves. - -Similarly, when parsing these values back, we want to split on `:` and then percent-decode the user/password parts individually, so we need to use [`unsafeToString`][UserInfo.unsafeToString] to ensure we get the encoded version. - -Another example where this sort of thing might be useful is if you would like to encode/decode spaces in paths as `+` rather than `%20`. Having the ability to hook into the parse/print stage and choose to examine or print with or without percent encoding/decoding applied gives us the flexibility to produce and consume values exactly as we want, rather than the library attempting to know best in all cases. - -### Host parsing - -The host printing/parsing setup is a little different. This is to accommodate something that lies outside of the RFC 3986 spec: multiple host definitions within a URI. The motivating case for this is things like connection strings for MongoDB, where host/port pairs can be defined separated by commas within a single URI: - -``` -mongodb://db1.example.net:27017,db2.example.net:2500/?replicaSet=test -``` +1. Module documentation is [published on Pursuit](https://pursuit.purescript.org/packages/purescript-uri). +2. Written documentation and [the changelog](./docs/CHANGELOG.md) are kept in [the docs directory](./docs). +3. Usage examples can be found in [the test suite](./test). -This doesn't jive with what is said in RFC 3986, as there a comma is allowed as part of a hostname, but the multiple ports don't fit into the schema. To get around this, when it comes to parsing hosts, the parsing is entirely handed over to the `parseHosts` parser in the options (in the cases for the other parameters, a normal function is run on a value that has been parsed according to the spec already). +If you get stuck, there are several ways to get help: -For normal URIs the [`HostPortPair`][HostPortPair] parser/printer should serve well enough. This accepts functions to deal with the host/port parts allowing for those aspects to be dealt with much like all the other options. +- [Open an issue](https://github.com/purescript-contrib/purescript-uri/issues) if you have encountered a bug or problem. +- [Search or start a thread on the PureScript Discourse](https://discourse.purescript.org) if you have general questions. You can also ask questions in the `#purescript` and `#purescript-beginners` channels on the [Functional Programming Slack](https://functionalprogramming.slack.com) ([invite link](https://fpchat-invite.herokuapp.com/)). -For URIs that are like the MongoDB connection string, this library provides [`URI.Extra.MultiHostPortPair`][MultiHostPortPair]. Given that both of these allow for custom `Host` / `Port` types, hopefully nobody else will need to write anything for the general host-section-parsing part! +## Contributing -## Further documentation +You can contribute to `uri` in several ways: -[The tests](test/) contain many examples of URI constructions using the basic types this library provides. +1. If you encounter a problem or have a question, please [open an issue](https://github.com/purescript-contrib/purescript-uri/issues). We'll do our best to work with you to resolve or answer it. -Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-uri). +2. If you would like to contribute code, tests, or documentation, please [read the contributor guide](./.github/CONTRIBUTING.md). It's a short, helpful introduction to contributing to this library, including development instructions. -[AbsoluteURI]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.AbsoluteURI -[Host]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.Host -[HostPortPair]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.HostPortPair -[MultiHostPortPair]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.Extra.MultiHostPortPair -[RelativeRef]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.RelativeRef -[URI]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.URI -[URIRef]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.URIRef -[UserInfo.fromString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:fromString -[UserInfo.toString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:toString -[UserInfo.unsafeFromString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:unsafeFromString -[UserInfo.unsafeToString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:unsafeToString -[UserInfo]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo -[UserPassInfo]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.Extra.UserPassInfo +3. If you have written a library, tutorial, guide, or other resource based on this package, please share it on the [PureScript Discourse](https://discourse.purescript.org)! Writing libraries and learning resources are a great way to help this library succeed. diff --git a/bower.json b/bower.json index c716183..a0f3b27 100755 --- a/bower.json +++ b/bower.json @@ -28,6 +28,7 @@ }, "devDependencies": { "purescript-quickcheck": "^6.1.0", - "purescript-spec": "^3.0.0" + "purescript-spec": "^4.0.1", + "purescript-aff": "^5.1.2" } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..bb34d86 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +Breaking changes (😱!!!): + +New features: + +Bugfixes: + +Other improvements: + +## [0.0.0] - 2020-01-01 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d4f17dd --- /dev/null +++ b/docs/README.md @@ -0,0 +1,134 @@ +# URI Documentation + +This directory contains documentation for `uri`. If you are interested in contributing new documentation, please read the [contributor guidelines](../.github/CONTRIBUTING.md) and [What Nobody Tells You About Documentation](https://documentation.divio.com) for help getting started. + +## Getting started + +The types and names here are a fairly faithful representation of the components described in the spec. + +- [`URI`][URI] is for absolutely specified URIs that can also have path, query, and fragment (hash) parts. +- [`AbsoluteURI`][AbsoluteURI] is a variation on `URI` that drops the ability for the URI to carry a fragment. +- [`RelativeRef`][RelativeRef] is for relatively specified URIs that can also have path, query, and fragment (hash) parts. +- [`URIRef`][URIRef] is combination of `URI` and `RelativeRef`, allowing the full range of representable URIs. + +The absolute/relative terminology when applied to URIs does not relate to the paths that a URI may carry, it refers to whether the URI has a "scheme" or not. For example `http://example.com` and `file://../test.txt` are absolute URIs but `//example.com` and `/test.txt` are relative. + +Assuming none of the `unsafe`-prefixed functions are used when constructing a URI, it should be impossible to construct a URI that is invalid using the types this library provides*. The slight downside of this is the data structures are relatively complex so as to only admit correct possibilities. + +\* Actually, there is one exception to that - `IPv6Address` is far too forgiving in what it allows currently. Contributions welcome! + +### URI component representations + +Due to the differing needs of users of this library, the URI types are all parameterised to allow for custom representations to be used for parts of the URI. Take a look at the most heavily parametrised type, `URIRef`: + +``` purescript +type URIRef userInfo hosts path hierPath relPath query fragment = ... +``` + +This allows us to provide hooks into the parsing and printing processes for a URI, so that types better suited to the intended use case can be used. + +Taking `userInfo` as an example, according to the spec, the `user-info` part of an authority is just an arbitrary string of characters terminated by an `@` before a hostname. An extremely common usage for this is the `user:password` scheme, so by leaving the choice of representation as a type variable we can switch it out for a type specifically designed to handle that (this library includes one actually, under [`URI.Extra.UserPassInfo`][UserPassInfo]). + +### App-specific URI type definitions + +When using this library, you'll probably want to define type synonyms for the URIs that make sense for your use case. A URI type that uses the simple representations for each component will look something like this: + +``` purescript +type MyURI = URIRef UserInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment +``` + +Along with these types, you'll want to define an options record that specifies how to parse and print URIs that look like this: + +``` purescript +options ∷ Record (URIRefOptions UserInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment) +options = + { parseUserInfo: pure + , printUserInfo: id + , parseHosts: HostPortPair.parser pure pure + , printHosts: HostPortPair.print id id + , parsePath: pure + , printPath: id + , parseHierPath: pure + , printHierPath: id + , parseRelPath: pure + , printRelPath: id + , parseQuery: pure + , printQuery: id + , parseFragment: pure + , printFragment: id + } +``` + +As you can see by all the `pure` and `id`, we're not doing a whole lot here. `parseHosts` is a bit of an exception, but that's just due to the way that case is handled (see [later in this README](#host-parsing) for more details about that). + +These types ([`UserInfo`][UserInfo], [`HostPortPair`][HostPortPair], [`Host`][Host], etc.) are all provided by the library, and where necessary can only be constructed via smart constructor. This ensures that percent-encoding is applied to characters where necessary to ensure the constructed values will print as valid URIs, and so on. + +If we decided that we wanted to support `user:password` style user-info, we'd modify this by changing our type to use [`UserPassInfo`][UserPassInfo]: + +``` purescript +type MyURI = URIRef UserPassInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment +``` + +And update our options to use the appropriate parse/print functions accordingly: + +``` purescript +options ∷ Record (URIRefOptions UserPassInfo (HostPortPair Host Port) Path HierPath RelPath Query Fragment) +options = + { parseUserInfo: UserPassInfo.parse + , printUserInfo: UserPassInfo.print + , ... +``` + +### Writing custom component types + +These `parse/print` functions all share much the same shape of signature. For the case in the previous example, they come out as: + +``` purescript +parseUserInfo ∷ UserInfo → Either URIPartParseError UserPassInfo +printUserInfo ∷ UserPassInfo → UserInfo +``` + +So you can see that for each component, when the options hooks/custom representation stuff is used, we take one of these library-provided component types and parse it into our new representation, and also print it back to that simple type later. + +Each of the library-provided component types have a `toString` function that extracts the inner value as a string after applying percent-decoding, and an `unsafeToString` that provides exactly the value that was parsed, preserving percent decoding. Similarly, there's a `fromString` that performs the minimal amount of required percent encoding for that part of the URI, and an `unsafeFromString` that performs no encoding at all. + +You may ask why it's ever useful to have access to the encoded values, or to be able to print without encoding, so here's a motivating example: + +For the [`UserPassInfo`][UserPassInfo] example, the typical way of encoding a username or password that contains a colon within it is to use `%3A` (`us:er` becomes `us%3Aer`). This allows colons-within-the-values to be recongised as independent from the colon-separating-username-and-password (`us%3Aer:password`). + +According to the spec it is not a requirement to encode colons in this part of the URI scheme, so just using [`toString`][UserInfo.toString] on `us:er` will get us back a `us:er`, resulting in `us:er:password`, so we'd have no way of knowing where the user ends and where the password starts. + +The solution when printing is to do some custom encoding that also replaces `:` with `%3A` for the user/password parts, and then joins them with the unencoded `:` afterwards. If we constructed the resulting [`UserInfo`][UserInfo] value with [`fromString`][UserInfo.fromString] it would re-encode our already encoded user/password parts (giving us `%253A` instead of `%3A`), so we use [`unsafeFromString`][UserInfo.unsafeFromString] since we've done the encoding ourselves. + +Similarly, when parsing these values back, we want to split on `:` and then percent-decode the user/password parts individually, so we need to use [`unsafeToString`][UserInfo.unsafeToString] to ensure we get the encoded version. + +Another example where this sort of thing might be useful is if you would like to encode/decode spaces in paths as `+` rather than `%20`. Having the ability to hook into the parse/print stage and choose to examine or print with or without percent encoding/decoding applied gives us the flexibility to produce and consume values exactly as we want, rather than the library attempting to know best in all cases. + +### Host parsing + +The host printing/parsing setup is a little different. This is to accommodate something that lies outside of the RFC 3986 spec: multiple host definitions within a URI. The motivating case for this is things like connection strings for MongoDB, where host/port pairs can be defined separated by commas within a single URI: + +``` +mongodb://db1.example.net:27017,db2.example.net:2500/?replicaSet=test +``` + +This doesn't jive with what is said in RFC 3986, as there a comma is allowed as part of a hostname, but the multiple ports don't fit into the schema. To get around this, when it comes to parsing hosts, the parsing is entirely handed over to the `parseHosts` parser in the options (in the cases for the other parameters, a normal function is run on a value that has been parsed according to the spec already). + +For normal URIs the [`HostPortPair`][HostPortPair] parser/printer should serve well enough. This accepts functions to deal with the host/port parts allowing for those aspects to be dealt with much like all the other options. + +For URIs that are like the MongoDB connection string, this library provides [`URI.Extra.MultiHostPortPair`][MultiHostPortPair]. Given that both of these allow for custom `Host` / `Port` types, hopefully nobody else will need to write anything for the general host-section-parsing part! + + +[AbsoluteURI]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.AbsoluteURI +[Host]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.Host +[HostPortPair]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.HostPortPair +[MultiHostPortPair]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.Extra.MultiHostPortPair +[RelativeRef]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.RelativeRef +[URI]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.URI +[URIRef]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.URIRef +[UserInfo.fromString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:fromString +[UserInfo.toString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:toString +[UserInfo.unsafeFromString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:unsafeFromString +[UserInfo.unsafeToString]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo#v:unsafeToString +[UserInfo]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.UserInfo +[UserPassInfo]: https://pursuit.purescript.org/packages/purescript-uri/docs/URI.Extra.UserPassInfo diff --git a/package.json b/package.json deleted file mode 100644 index 25060a0..0000000 --- a/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "private": true, - "scripts": { - "clean": "rimraf output && rimraf .pulp-cache", - "build": "eslint src && pulp build -- --censor-lib --strict", - "test": "pulp test" - }, - "devDependencies": { - "eslint": "^4.4.1", - "pulp": "^11.0.2", - "purescript": "^0.12.0", - "purescript-psa": "^0.5.1", - "rimraf": "^2.6.1" - } -} diff --git a/packages.dhall b/packages.dhall new file mode 100644 index 0000000..2aace74 --- /dev/null +++ b/packages.dhall @@ -0,0 +1,4 @@ +let upstream = + https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20200822/packages.dhall sha256:b4f151f1af4c5cb6bf5437489f4231fbdd92792deaf32971e6bcb0047b3dd1f8 + +in upstream diff --git a/spago.dhall b/spago.dhall new file mode 100644 index 0000000..1bca860 --- /dev/null +++ b/spago.dhall @@ -0,0 +1,17 @@ +{ name = "uri" +, dependencies = + [ "aff" + , "arrays" + , "generics-rep" + , "globals" + , "integers" + , "parsing" + , "profunctor-lenses" + , "quickcheck" + , "these" + , "unfoldable" + , "spec" + ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/Main.purs b/test/Main.purs index 8136ef2..ea6bc04 100755 --- a/test/Main.purs +++ b/test/Main.purs @@ -3,6 +3,7 @@ module Test.Main where import Prelude import Effect (Effect) +import Effect.Aff (launchAff_) import Test.Spec.Reporter (consoleReporter) import Test.Spec.Runner (run) import Test.URI.AbsoluteURI as AbsoluteURI @@ -20,7 +21,7 @@ import Test.URI.UserInfo as UserInfo main ∷ Effect Unit -main = run [consoleReporter] do +main = launchAff_ $ run [consoleReporter] do Scheme.spec UserInfo.spec Host.spec