diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 237840d..2bcda4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,7 +137,7 @@ jobs: stack test --no-rerun-tests http-types:test:spec ${{ matrix.resolver }} - name: Test Docs run: | - stack test --no-rerun-tests http-types:test:doctests ${{ matrix.resolver }} + stack test http-types:test:doctests ${{ matrix.resolver }} # # We probably want to add benchmarks at some point, just to make sure # # functions don't regress in performance too much? # - name: Bench diff --git a/.gitignore b/.gitignore index aad3535..ed39e63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ *~ .cabal-sandbox cabal.sandbox.config -dist/* -dist-newstyle/* +dist/ +dist-newstyle/ *_flymake.hs *.lock -.stack-work/* -test/golden/*.actual +.stack-work/ test/.golden/**/actual *.code-workspace +*.local diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ca5e53..ab1cf6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,20 @@ ## 0.12.5 [XXXX-XX-XX] +* Add status `451 Unavailable For Legal Reasons` +* Add `http30` as a shortcut for `HttpVersion 3 0` * Export everything from `Network.HTTP.Types` * Added a bunch of regression, unit and property tests for stability. * Updated the `README.md` +* Lowest GHC version supported is now `7.10.3`. + Adjusted dependency constraints to reflect this. +* Removed explicit `Typeable` class derivations since that happens automatically + since GHC `7.10` ## 0.12.4 [2023-11-29] -* Add `Data` and `Generic` instances to `ByteRange`, `StdMethod`, `Status` and `HttpVersion`. +* Add `Data` and `Generic` instances to `ByteRange`, `StdMethod`, `Status` + and `HttpVersion`. * Rework of all the documentation, with the addition of `@since` notations. ## 0.12.3 [2019-02-24] diff --git a/Network/HTTP/Types.hs b/Network/HTTP/Types.hs index ec4deeb..e474b0d 100644 --- a/Network/HTTP/Types.hs +++ b/Network/HTTP/Types.hs @@ -31,6 +31,7 @@ module Network.HTTP.Types ( http10, http11, http20, + http30, -- * Status @@ -122,6 +123,8 @@ module Network.HTTP.Types ( tooManyRequests429, status431, requestHeaderFieldsTooLarge431, + status451, + unavailableForLegalReasons451, status500, internalServerError500, status501, diff --git a/Network/HTTP/Types/Header.hs b/Network/HTTP/Types/Header.hs index b37e1bf..023e04b 100644 --- a/Network/HTTP/Types/Header.hs +++ b/Network/HTTP/Types/Header.hs @@ -96,9 +96,6 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.CaseInsensitive as CI import Data.Data (Data) import Data.List (intersperse) -#if __GLASGOW_HASKELL__ < 710 -import Data.Monoid -#endif import GHC.Generics (Generic) -- | A full HTTP header field with the name and value separated. @@ -451,7 +448,9 @@ hPrefer = "Prefer" hPreferenceApplied :: HeaderName hPreferenceApplied = "Preference-Applied" --- | An individual byte range. +-- | An individual byte range. Used in @Range@ request headers. +-- This type and its accompanying functions are /NOT/ compatible with the +-- @Content-Range@ response header. -- -- Negative indices are not allowed! -- @@ -554,8 +553,13 @@ parseByteRanges bs1 = do (r, bs5) <- range bs4 ranges (front . (r :)) bs5 - -- FIXME: Use 'stripPrefix' from the 'bytestring' package. - -- Might have to update the dependency constraints though. - stripPrefixB x y - | x `B.isPrefixOf` y = Just (B.drop (B.length x) y) - | otherwise = Nothing +stripPrefixB :: B.ByteString -> B.ByteString -> Maybe B.ByteString +#if !MIN_VERSION_bytestring(0,10,8) +-- FIXME: Use 'stripPrefix' from the 'bytestring' package. +-- Might have to update the dependency constraints though. +stripPrefixB x y + | x `B.isPrefixOf` y = Just (B.drop (B.length x) y) + | otherwise = Nothing +#else +stripPrefixB = B.stripPrefix +#endif diff --git a/Network/HTTP/Types/Method.hs b/Network/HTTP/Types/Method.hs index 779e586..d240b3a 100644 --- a/Network/HTTP/Types/Method.hs +++ b/Network/HTTP/Types/Method.hs @@ -52,39 +52,39 @@ import GHC.Generics (Generic) -- | HTTP method (flat 'B.ByteString' type). type Method = B.ByteString --- | HTTP GET Method +-- | GET Method methodGet :: Method methodGet = renderStdMethod GET --- | HTTP POST Method +-- | POST Method methodPost :: Method methodPost = renderStdMethod POST --- | HTTP HEAD Method +-- | HEAD Method methodHead :: Method methodHead = renderStdMethod HEAD --- | HTTP PUT Method +-- | PUT Method methodPut :: Method methodPut = renderStdMethod PUT --- | HTTP DELETE Method +-- | DELETE Method methodDelete :: Method methodDelete = renderStdMethod DELETE --- | HTTP TRACE Method +-- | TRACE Method methodTrace :: Method methodTrace = renderStdMethod TRACE --- | HTTP CONNECT Method +-- | CONNECT Method methodConnect :: Method methodConnect = renderStdMethod CONNECT --- | HTTP OPTIONS Method +-- | OPTIONS Method methodOptions :: Method methodOptions = renderStdMethod OPTIONS --- | HTTP PATCH Method +-- | PATCH Method -- -- @since 0.8.0 methodPatch :: Method diff --git a/Network/HTTP/Types/Status.hs b/Network/HTTP/Types/Status.hs index 24f94db..172433b 100644 --- a/Network/HTTP/Types/Status.hs +++ b/Network/HTTP/Types/Status.hs @@ -101,6 +101,8 @@ module Network.HTTP.Types.Status ( tooManyRequests429, status431, requestHeaderFieldsTooLarge431, + status451, + unavailableForLegalReasons451, status500, internalServerError500, status501, @@ -221,6 +223,7 @@ instance Enum Status where toEnum 428 = status428 toEnum 429 = status429 toEnum 431 = status431 + toEnum 451 = status451 toEnum 500 = status500 toEnum 501 = status501 toEnum 502 = status502 @@ -236,6 +239,8 @@ instance Bounded Status where maxBound = status511 -- | Create a 'Status' from a status code and message. +-- +-- @since 0.7.3 mkStatus :: Int -> B.ByteString -> Status mkStatus = Status @@ -645,61 +650,75 @@ unprocessableEntity422 :: Status unprocessableEntity422 = status422 -- | Upgrade Required 426 --- () +-- () -- -- @since 0.10 status426 :: Status status426 = mkStatus 426 "Upgrade Required" -- | Upgrade Required 426 --- () +-- () -- -- @since 0.10 upgradeRequired426 :: Status upgradeRequired426 = status426 -- | Precondition Required 428 --- () +-- () -- -- @since 0.8.5 status428 :: Status status428 = mkStatus 428 "Precondition Required" -- | Precondition Required 428 --- () +-- () -- -- @since 0.8.5 preconditionRequired428 :: Status preconditionRequired428 = status428 -- | Too Many Requests 429 --- () +-- () -- -- @since 0.8.5 status429 :: Status status429 = mkStatus 429 "Too Many Requests" -- | Too Many Requests 429 --- () +-- () -- -- @since 0.8.5 tooManyRequests429 :: Status tooManyRequests429 = status429 -- | Request Header Fields Too Large 431 --- () +-- () -- -- @since 0.8.5 status431 :: Status status431 = mkStatus 431 "Request Header Fields Too Large" -- | Request Header Fields Too Large 431 --- () +-- () -- -- @since 0.8.5 requestHeaderFieldsTooLarge431 :: Status requestHeaderFieldsTooLarge431 = status431 +-- | Unavailable For Legal Reasons 451 +-- () +-- +-- @since 0.12.5 +status451 :: Status +status451 = mkStatus 451 "Unavailable For Legal Reasons" + +-- | Unavailable For Legal Reasons 451 +-- () +-- +-- @since 0.13 +unavailableForLegalReasons451 :: Status +unavailableForLegalReasons451 = status451 + -- | Internal Server Error 500 status500 :: Status status500 = mkStatus 500 "Internal Server Error" @@ -769,14 +788,14 @@ httpVersionNotSupported505 :: Status httpVersionNotSupported505 = status505 -- | Network Authentication Required 511 --- () +-- () -- -- @since 0.8.5 status511 :: Status status511 = mkStatus 511 "Network Authentication Required" -- | Network Authentication Required 511 --- () +-- () -- -- @since 0.8.5 networkAuthenticationRequired511 :: Status diff --git a/Network/HTTP/Types/URI.hs b/Network/HTTP/Types/URI.hs index f81e24a..8d34f18 100644 --- a/Network/HTTP/Types/URI.hs +++ b/Network/HTTP/Types/URI.hs @@ -91,9 +91,6 @@ import qualified Data.ByteString.Lazy as BL import Data.Char (ord) import Data.List (intersperse) import Data.Maybe (fromMaybe) -#if __GLASGOW_HASKELL__ < 710 -import Data.Monoid -#endif import Data.Text (Text) import Data.Text.Encoding (decodeUtf8With, encodeUtf8) import Data.Text.Encoding.Error (lenientDecode) @@ -275,6 +272,12 @@ ord8 :: Char -> Word8 ord8 = fromIntegral . ord unreservedQS, unreservedPI :: [Word8] +-- FIXME: According to RFC 3986, the following are also allowed in query segments: +-- "!'()*;:@&=+$,/?" +-- +-- https://www.rfc-editor.org/rfc/rfc3986#section-3.4 +-- +-- Incidentally, this is also the list of unreserved characters for fragments. unreservedQS = map ord8 "-_.~" -- FIXME: According to RFC 3986, the following are also allowed in path segments: -- "!'()*;" @@ -542,7 +545,7 @@ renderQueryPartialEscape qm = -- @since 0.12.1 renderQueryBuilderPartialEscape :: Bool -> PartialEscapeQuery -> B.Builder renderQueryBuilderPartialEscape _ [] = mempty --- FIXME replace mconcat + map with foldr +-- FIXME: replace mconcat + map with foldr renderQueryBuilderPartialEscape qmark' (p : ps) = mconcat $ go (if qmark' then qmark else mempty) p diff --git a/Network/HTTP/Types/Version.hs b/Network/HTTP/Types/Version.hs index 1cd230c..6a68731 100644 --- a/Network/HTTP/Types/Version.hs +++ b/Network/HTTP/Types/Version.hs @@ -8,6 +8,7 @@ module Network.HTTP.Types.Version ( http10, http11, http20, + http30, ) where import Data.Data (Data) @@ -51,3 +52,9 @@ http11 = HttpVersion 1 1 -- @since 0.10 http20 :: HttpVersion http20 = HttpVersion 2 0 + +-- | HTTP 3.0 +-- +-- @since 0.12.5 +http30 :: HttpVersion +http30 = HttpVersion 3 0 diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..71e494e --- /dev/null +++ b/cabal.project @@ -0,0 +1,5 @@ +packages: ./http-types.cabal +ignore-project: False +allow-newer: Cabal-3.14.2.0:containers, + Cabal-syntax-3.14.2.0:containers +jobs: 2 diff --git a/http-types.cabal b/http-types.cabal index ad604a5..9ea5c02 100644 --- a/http-types.cabal +++ b/http-types.cabal @@ -5,13 +5,15 @@ Synopsis: Generic HTTP types for Haskell (for both client and server Description: Types and functions to describe and handle HTTP concepts. Including "methods", "headers", "query strings", "paths" and "HTTP versions". Homepage: https://github.com/Vlix/http-types -License: BSD3 +License: BSD-3-Clause License-file: LICENSE Author: Aristid Breitkreuz, Michael Snoyman Maintainer: felix.paulusma@gmail.com -Copyright: (C) 2011 Aristid Breitkreuz +Copyright: (C) 2011 Aristid Breitkreuz, (C) 2023 Felix Paulusma Category: Network, Web Build-type: Simple +Tested-with: + GHC == 7.10.3, GHC == 9.6.7, GHC == 9.8.4, GHC == 9.10.3, GHC == 9.12.4, GHC == 9.14.1 Extra-source-files: README.md CHANGELOG.md @@ -60,7 +62,7 @@ Test-suite spec case-insensitive, filepath, hspec >= 1.3, - hspec-golden, + hspec-golden >= 0.2, http-types, QuickCheck, quickcheck-instances, diff --git a/test/Network/HTTP/Types/HeaderSpec.hs b/test/Network/HTTP/Types/HeaderSpec.hs index d10c1ec..86a290e 100644 --- a/test/Network/HTTP/Types/HeaderSpec.hs +++ b/test/Network/HTTP/Types/HeaderSpec.hs @@ -103,7 +103,7 @@ headerCheck (hdr, msg) = do it (B8.unpack . pad $ original msg) $ hdr `shouldBe` msg where pad bs = - let padding = B8.pack $ replicate (maxMsg - B.length bs) ' ' + let padding = B8.replicate (maxMsg - B.length bs) ' ' in bs <> padding maxMsg :: Int diff --git a/test/Network/HTTP/Types/StatusSpec.hs b/test/Network/HTTP/Types/StatusSpec.hs index 6f06701..7f8f700 100644 --- a/test/Network/HTTP/Types/StatusSpec.hs +++ b/test/Network/HTTP/Types/StatusSpec.hs @@ -102,6 +102,7 @@ _400Statusses = , (status428, preconditionRequired428, 428, "Precondition Required") , (status429, tooManyRequests429, 429, "Too Many Requests") , (status431, requestHeaderFieldsTooLarge431, 431, "Request Header Fields Too Large") + , (status451, unavailableForLegalReasons451, 451, "Unavailable For Legal Reasons") ] _500Statusses :: [StatusTuple] @@ -131,7 +132,7 @@ statusCheck (st, st', code, msg) = do st `shouldBe` st' where pad bs = - let padding = B8.pack $ replicate (maxMsg - B.length bs) ' ' + let padding = B8.replicate (maxMsg - B.length bs) ' ' in bs <> padding maxMsg :: Int diff --git a/test/Network/HTTP/Types/URISpec.hs b/test/Network/HTTP/Types/URISpec.hs index 1c75493..5e5591c 100644 --- a/test/Network/HTTP/Types/URISpec.hs +++ b/test/Network/HTTP/Types/URISpec.hs @@ -155,15 +155,8 @@ mkGoldenFile name content = encodePretty = B8.unpack, writeToFile = B.writeFile, readFromFile = B.readFile, -#if MIN_VERSION_hspec_golden(0,2,0) - -- Has this format because 'hspec-golden-0.1.0.3' has it as default - -- and we want to keep it the same until we drop LTS-18 goldenFile = goldenDir name "golden", actualFile = Just (goldenDir name "actual"), -#else - testName = name, - directory = goldenDir, -#endif failFirstTime = False } diff --git a/test/Network/HTTP/Types/VersionSpec.hs b/test/Network/HTTP/Types/VersionSpec.hs index 90c454a..1a80923 100644 --- a/test/Network/HTTP/Types/VersionSpec.hs +++ b/test/Network/HTTP/Types/VersionSpec.hs @@ -1,5 +1,3 @@ -{-# LANGUAGE OverloadedStrings #-} - module Network.HTTP.Types.VersionSpec (main, spec) where import Test.Hspec @@ -14,19 +12,18 @@ spec = describe "Regression tests" $ mapM_ checkVersion allVersions --- | [("Rendered", {constant}, {literal}, "Shown")] -allVersions :: [(String, HttpVersion, HttpVersion, String)] +-- | [("Rendered", {constant}, {literal})] +allVersions :: [(String, HttpVersion, HttpVersion)] allVersions = - [ ("HTTP/0.9", http09, HttpVersion 0 9, "HTTP/0.9") - , ("HTTP/1.0", http10, HttpVersion 1 0, "HTTP/1.0") - , ("HTTP/1.1", http11, HttpVersion 1 1, "HTTP/1.1") - , ("HTTP/2.0", http20, HttpVersion 2 0, "HTTP/2.0") + [ ("HTTP/0.9", http09, HttpVersion 0 9) + , ("HTTP/1.0", http10, HttpVersion 1 0) + , ("HTTP/1.1", http11, HttpVersion 1 1) + , ("HTTP/2.0", http20, HttpVersion 2 0) + , ("HTTP/3.0", http30, HttpVersion 3 0) ] -checkVersion :: (String, HttpVersion, HttpVersion, String) -> Spec -checkVersion (msg, v1, v2, str) = - it msg $ do +checkVersion :: (String, HttpVersion, HttpVersion) -> Spec +checkVersion (str, v1, v2) = + it str $ do v1 `shouldBe` v2 show v1 `shouldBe` str - --- it "parses to HTTP/3" $ http30 `shouldBe` HttpVersion 3 0 diff --git a/test/golden/urlEncode-path.golden b/test/golden/urlEncode-path.golden deleted file mode 100644 index 5900b15..0000000 --- a/test/golden/urlEncode-path.golden +++ /dev/null @@ -1 +0,0 @@ -%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23$%25&%27%28%29%2A+,-.%2F0123456789:%3B%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF \ No newline at end of file diff --git a/test/golden/urlEncode-query.golden b/test/golden/urlEncode-query.golden deleted file mode 100644 index b725385..0000000 --- a/test/golden/urlEncode-query.golden +++ /dev/null @@ -1 +0,0 @@ -%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF \ No newline at end of file