stew / codebase

0 stars 2 forks source link

Overhaul of Path and Query #11

Closed ceedubs closed 2 years ago

ceedubs commented 2 years ago

This overhauls the Path and Query types to make them more user-friendly and to allow for properly encoded URIs.

It also fixes a bug in which the query string wasn't being included in the request.

I threw in some totally unrelated changes to make the functions that return Either Failure a follow a try prefix naming convention (example: tryPost) with the non-try version using Exception instead of Either.

Resolves #10.

README

Below is the new README after these changes.

This is an HTTP client library. It can be used to make HTTP requests and inspect their responses.

Usage

Below is an example of making a simple HTTP request and getting back a response. It uses the & helper for creating a Query (which will be converted to a URI query string).

  examples.query : '{IO, Exception} Response
  examples.query _ =
    use Query empty
    unisonWeb = Authority None (HostName "www.unisonweb.org") None
    path =
      use Path /
      Path.root / "docs" / "search"
    query = empty & ("term", "List.map") & ("version", "1.0")
    uri = Uri.Uri https (Some unisonWeb) path empty None
    req = Request.get uri
    handle Http.request req with Http.handler

Response Status

By default, Http.handler does not return a Failure for a non-success HTTP status code (such as 500 Internal Server Error). It is left up to the user to determine whether they want to treat a 404 as an error or as an expected case which they should handle accordingly (for example by returning None). You can use Response.isSuccess to check whether a response has a success code. In the future we may want to provide some helper methods for common use-cases of status code handling.

Response Body

The response body is treated as raw bytes.

  unique type Body = Body Bytes

  body : Response -> Optional Body

This library handles decoding chunked and compressed responses but it is up to the user to further interpret those bytes. For example you may want to use fromUtf8 if you are expecting a text response, and/or you may want to use a JSON library to parse the response as JSON. In the future we may add more helper methods for common use-cases.

URI Encoding

You should not attempt to URI-encode the segments in the Path or the keys/values in the Query. This library will automatically encode these values when serializing the HTTP request.

Trailing Slash

According to the HTTP specification, http://www.unisonweb.org/docs/quickstart and http://www.unisonweb.org/docs/quickstart/ (with a trailing slash) are two different URIs. The URI without the trailing slash has two path segments: docs and quickstart. The URI with the trailing slash technically has a third path segment that is an empty string. Therefore if you need to create a path with a trailing slash you can add an empty segment to the end:

  trailingSlash =
    use Path /
    Path.root / "docs" / "language-reference" / ""

  unsafeRun! '(fromUtf8 (Path.encode trailingSlash))
  ⧨
  "/docs/language-reference/"

Inspiration

This library was heavily inspired by the excellent http4s Scala library.

The changes summarized below are available for you to review, using the following command:

pull-request.load git@github.com:stew/codebase:.http.trunk git@github.com:ceedubs/unison-dev:.pr.http.overhaul

Updates:

 unique type Path

 ↓
 unique type Path
   +  unisoncomputing2021 : License
   +  authors.stew        : Author
   +  authors.ceedubs     : Author

 unique type Query

 ↓
 unique type Query
   +  unisoncomputing2021 : License
   +  authors.ceedubs     : Author

 unique type Uri

 ↓
 unique type Uri
   +  unisoncomputing2021 : License
   +  authors.stew        : Author
   +  authors.ceedubs     : Author

 Http.delete : chunks.Uri ->{trunk.Http} Either Failure Response
 ↓
 Http.delete : Uri ->{Exception, Http} Response
 -  authors.stew        : Author
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Http.get : chunks.Uri ->{trunk.Http} Either Failure Response
 ↓
 Http.get : Uri ->{Exception, Http} Response
 -  authors.stew        : Author
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Http.handler : base.Request {trunk.Http} a ->{IO, Exception} a
 ↓
 Http.handler : base.Request {Http} a ->{IO} a
 +  unisoncomputing2021 : License
 +  authors.stew        : Author
 +  authors.ceedubs     : Author

 Http.patch : chunks.Uri -> Body ->{trunk.Http} Either Failure Response
 ↓
 Http.patch : Uri -> Body ->{Exception, Http} Response
 -  authors.stew        : Author
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Http.post : chunks.Uri ->{trunk.Http} Body ->{trunk.Http} Either Failure Response
 ↓
 Http.post : Uri -> Body ->{Exception, Http} Response
 -  authors.stew        : Author
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Http.put : chunks.Uri -> Body ->{trunk.Http} Either Failure Response
 ↓
 Http.put : Uri -> Body ->{Exception, Http} Response
 -  authors.stew        : Author
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Http.request : trunk.Request ->{trunk.Http} Either Failure Response
 ↓
 Http.request : Request ->{Exception, Http} Response
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Path.Path : Text -> chunks.Path
 ↓
 Path.Path : [Text] -> Path

 Query.Query : Text -> chunks.Query
 ↓
 Query.Query : Map Text [Text] -> Query

 Request.asBytes : trunk.Request -> Bytes
 ↓
 Request.asBytes : Request -> Bytes
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Request.requestLine : trunk.Request -> Bytes
 ↓
 Request.requestLine : Request -> Bytes
 +  unisoncomputing2021 : License
 +  authors.stew        : Author
 +  authors.ceedubs     : Author

 test.aol : chunks.Uri
 ↓
 test.aol : Uri
 +  unisoncomputing2021 : License
 +  authors.stew        : Author
 +  authors.ceedubs     : Author

 test.bibo : chunks.Uri
 ↓
 test.bibo : Uri
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 test.unisonDocs : chunks.Uri
 ↓
 test.unisonDocs : Uri
 +  unisoncomputing2021 : License
 +  authors.stew        : Author
 +  authors.ceedubs     : Author

 Uri.path : chunks.Uri -> Text
 ↓
 Uri.path : Uri -> Path
 +  unisoncomputing2021 : License
 +  authors.ceedubs     : Author

 Uri.Uri : Scheme
 -> Optional Authority
 -> chunks.Path
 -> Optional chunks.Query
 -> Optional Fragment
 -> chunks.Uri
 ↓
 Uri.Uri : Scheme -> Optional Authority -> Path -> Query -> Optional Fragment -> Uri

There were 22 auto-propagated updates.

 patch patch (added 25 updates,
deleted 25)

Added definitions:

 Http.tryRequest                  : Request ->{Http} Either Failure Response (+1 metadata)
 Query.&                          : Query -> (Text, Text) -> Query (+2 metadata)
 Path./                           : Path -> Text -> Path (+2 metadata)
 README                           : Doc (+2 metadata)
 Query.addParam                   : Text -> Text -> Query -> Query (+2 metadata)
 Response.body                    : Response -> Optional Body (+2 metadata)
 Path.encode.char                 : Char -> Bytes (+2 metadata)
 Query.encode.char                : Char -> Bytes (+2 metadata)
 Query.empty                      : Query (+2 metadata)
 Query.encode.tests.empty         : [Test.Result] (+3 metadata)
 Path.encode                      : Path -> Bytes (+2 metadata)
 Query.encode                     : Query -> Bytes (+2 metadata)
 up.List.foldDelimited            : ∀ a b g2 g1 g.
                                    (b ->{g2} b ->{g1} b)
                                    -> (a ->{g} b)
                                    -> b
                                    -> b
                                    -> b
                                    -> [a]
                                    ->{g2, g1, g} b (+2 metadata)
 Scheme.http                      : Scheme (+2 metadata)
 Scheme.https                     : Scheme (+2 metadata)
 Query.params.modify              : (Map Text [Text] ->{g} Map Text [Text])
                                  -> Query
                                  ->{g} Query (+2 metadata)
 Path.segments.modify             : ([Text] ->{g} [Text]) -> Path ->{g} Path (+2 metadata)
 Query.params                     : Query -> Map Text [Text] (+2 metadata)
 examples.query                   : '{IO, Exception} Response (+2 metadata)
 Path.root                        : Path (+2 metadata)
 Path.encode.segment              : Text -> Bytes (+2 metadata)
 Path.segments                    : Path -> [Text] (+2 metadata)
 Query.params.set                 : Map Text [Text] -> Query -> Query (+2 metadata)
 Path.segments.set                : [Text] -> Path -> Path (+2 metadata)
 Query.encode.tests.double.simple : [Test.Result] (+3 metadata)
 examples.simple                  : '{IO, Exception} Response (+2 metadata)
 Query.encode.tests.single.simple : [Test.Result] (+3 metadata)
 Query.singleton                  : Text -> Text -> Query (+2 metadata)
 Uri.encode.percent.char.special  : Char -> Bytes (+2 metadata)
 Path.encode.char.test            : [Test.Result] (+3 metadata)
 Query.encode.char.test           : [Test.Result] (+3 metadata)
 Request.requestLine.tests        : [Test.Result] (+3 metadata)
 Path.slash.tests                 : [Test.Result] (+3 metadata)
 Query.encode.text                : Text -> Bytes (+2 metadata)
 examples.trailingSlash           : Path (+2 metadata)
 Http.tryDelete                   : Uri ->{Http} Either Failure Response (+3 metadata)
 Http.tryGet                      : Uri ->{Http} Either Failure Response (+3 metadata)
 Http.tryPatch                    : Uri -> Body ->{Http} Either Failure Response (+3 metadata)
 Http.tryPost                     : Uri
                                  ->{Http} Body
                                  ->{Http} Either Failure Response (+3 metadata)
 Http.tryPut                      : Uri -> Body ->{Http} Either Failure Response (+3 metadata)
 Query.union                      : Query -> Query -> Query (+2 metadata)
stew commented 2 years ago

this looks great

ceedubs commented 2 years ago

This was merged.