servant-multipart-api (empty) → 0.12.1
raw patch · 5 files changed
+319/−0 lines, 5 filesdep +basedep +bytestringdep +servantsetup-changed
Dependencies added: base, bytestring, servant, text, transformers
Files
- CHANGELOG.md +5/−0
- LICENSE +30/−0
- Setup.hs +2/−0
- servant-multipart-api.cabal +38/−0
- src/Servant/Multipart/API.hs +244/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+0.12.1+------++- First release of split package,+ [#51](https://github.com/haskell-servant/servant-multipart/pull/51)
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Alp Mestanogullari (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Alp Mestanogullari nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ servant-multipart-api.cabal view
@@ -0,0 +1,38 @@+name: servant-multipart-api+version: 0.12.1+synopsis: multipart/form-data (e.g file upload) support for servant+description:+ This package contains servant API types that support multiform upload, used by+ servant-multipart and servant-multipart-client for backend/client implementation.++homepage: https://github.com/haskell-servant/servant-multipart#readme+license: BSD3+license-file: LICENSE+author: Alp Mestanogullari+maintainer: alpmestan@gmail.com+copyright: 2016-2017 Alp Mestanogullari, 2018-2019 Servant Contributors+category: Web, Servant+build-type: Simple+cabal-version: >=1.10+extra-source-files: CHANGELOG.md+tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.4++library+ default-language: Haskell2010+ hs-source-dirs: src+ exposed-modules: Servant.Multipart.API++ -- ghc boot libs+ build-depends:+ base >=4.9 && <5+ , bytestring >=0.10.8.1 && <0.11+ , text >=1.2.3.0 && <1.3+ , transformers >=0.5.2.0 && <0.6++ -- other dependencies+ build-depends:+ servant >=0.16 && <0.19++source-repository head+ type: git+ location: https://github.com/haskell-servant/servant-multipart
+ src/Servant/Multipart/API.hs view
@@ -0,0 +1,244 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeApplications #-}+-- | @multipart/form-data@ Servant API support for servant.+-- see servant-multipart and servant-multipart-client for server- and client-+-- definitions.+--+-- This is mostly useful for adding file upload support to+-- an API. See haddocks of 'MultipartForm' for an introduction.+module Servant.Multipart.API+ ( MultipartForm+ , MultipartForm'+ , MultipartData(..)+ , ToMultipart(..)+ , FromMultipart(..)+ , MultipartResult+ , Tmp+ , Mem+ , Input(..)+ , FileData(..)+ ) where++import Data.Text (Text)+import Data.Typeable+import Servant.API++import qualified Data.ByteString.Lazy as LBS++-- | Combinator for specifying a @multipart/form-data@ request+-- body, typically (but not always) issued from an HTML @\<form\>@.+--+-- @multipart/form-data@ can't be made into an ordinary content+-- type for now in servant because it doesn't just decode the+-- request body from some format but also performs IO in the case+-- of writing the uploaded files to disk, e.g in @/tmp@, which is+-- not compatible with servant's vision of a content type as things+-- stand now. This also means that 'MultipartForm' can't be used in+-- conjunction with 'ReqBody' in an endpoint.+--+-- The 'tag' type parameter instructs the function to handle data+-- either as data to be saved to temporary storage ('Tmp') or saved to+-- memory ('Mem').+--+-- The 'a' type parameter represents the Haskell type to which+-- you are going to decode the multipart data to, where the+-- multipart data consists in all the usual form inputs along+-- with the files sent along through @\<input type="file"\>@+-- fields in the form.+--+-- One option provided out of the box by this library is to decode+-- to 'MultipartData'.+--+-- Example:+--+-- @+-- type API = MultipartForm Tmp (MultipartData Tmp) :> Post '[PlainText] String+--+-- api :: Proxy API+-- api = Proxy+--+-- server :: MultipartData Tmp -> Handler String+-- server multipartData = return str+--+-- where str = "The form was submitted with "+-- ++ show nInputs ++ " textual inputs and "+-- ++ show nFiles ++ " files."+-- nInputs = length (inputs multipartData)+-- nFiles = length (files multipartData)+-- @+--+-- You can alternatively provide a 'FromMultipart' instance+-- for some type of yours, allowing you to regroup data+-- into a structured form and potentially selecting+-- a subset of the entire form data that was submitted.+--+-- Example, where we only look extract one input, /username/,+-- and one file, where the corresponding input field's /name/+-- attribute was set to /pic/:+--+-- @+-- data User = User { username :: Text, pic :: FilePath }+--+-- instance FromMultipart Tmp User where+-- fromMultipart multipartData =+-- User \<$\> lookupInput "username" multipartData+-- \<*\> fmap fdPayload (lookupFile "pic" multipartData)+--+-- type API = MultipartForm Tmp User :> Post '[PlainText] String+--+-- server :: User -> Handler String+-- server usr = return str+--+-- where str = username usr ++ "'s profile picture"+-- ++ " got temporarily uploaded to "+-- ++ pic usr ++ " and will be removed from there "+-- ++ " after this handler has run."+-- @+--+-- Note that the behavior of this combinator is configurable,+-- by using 'serveWith' from servant-server instead of 'serve',+-- which takes an additional 'Context' argument. It simply is an+-- heterogeneous list where you can for example store+-- a value of type 'MultipartOptions' that has the configuration that+-- you want, which would then get picked up by servant-multipart.+--+-- __Important__: as mentionned in the example above,+-- the file paths point to temporary files which get removed+-- after your handler has run, if they are still there. It is+-- therefore recommended to move or copy them somewhere in your+-- handler code if you need to keep the content around.+type MultipartForm tag a = MultipartForm' '[] tag a++-- | 'MultipartForm' which can be modified with 'Servant.API.Modifiers.Lenient'.+data MultipartForm' (mods :: [*]) tag a++-- | What servant gets out of a @multipart/form-data@ form submission.+--+-- The type parameter 'tag' tells if 'MultipartData' is stored as a+-- temporary file or stored in memory. 'tag' is type of either 'Mem'+-- or 'Tmp'.+--+-- The 'inputs' field contains a list of textual 'Input's, where+-- each input for which a value is provided gets to be in this list,+-- represented by the input name and the input value. See haddocks for+-- 'Input'.+--+-- The 'files' field contains a list of files that were sent along with the+-- other inputs in the form. Each file is represented by a value of type+-- 'FileData' which among other things contains the path to the temporary file+-- (to be removed when your handler is done running) with a given uploaded+-- file's content. See haddocks for 'FileData'.+data MultipartData tag = MultipartData+ { inputs :: [Input]+ , files :: [FileData tag]+ }++-- | Representation for an uploaded file, usually resulting from+-- picking a local file for an HTML input that looks like+-- @\<input type="file" name="somefile" /\>@.+data FileData tag = FileData+ { fdInputName :: Text -- ^ @name@ attribute of the corresponding+ -- HTML @\<input\>@+ , fdFileName :: Text -- ^ name of the file on the client's disk+ , fdFileCType :: Text -- ^ MIME type for the file+ , fdPayload :: MultipartResult tag+ -- ^ path to the temporary file that has the+ -- content of the user's original file. Only+ -- valid during the execution of your handler as+ -- it gets removed right after, which means you+ -- really want to move or copy it in your handler.+ }++deriving instance Eq (MultipartResult tag) => Eq (FileData tag)+deriving instance Show (MultipartResult tag) => Show (FileData tag)++-- | Representation for a textual input (any @\<input\>@ type but @file@).+--+-- @\<input name="foo" value="bar"\ />@ would appear as @'Input' "foo" "bar"@.+data Input = Input+ { iName :: Text -- ^ @name@ attribute of the input+ , iValue :: Text -- ^ value given for that input+ } deriving (Eq, Show)++-- | 'MultipartData' is the type representing+-- @multipart/form-data@ form inputs. Sometimes+-- you may instead want to work with a more structured type+-- of yours that potentially selects only a fraction of+-- the data that was submitted, or just reshapes it to make+-- it easier to work with. The 'FromMultipart' class is exactly+-- what allows you to tell servant how to turn "raw" multipart+-- data into a value of your nicer type.+--+-- @+-- data User = User { username :: Text, pic :: FilePath }+--+-- instance FromMultipart Tmp User where+-- fromMultipart form =+-- User \<$\> lookupInput "username" (inputs form)+-- \<*\> fmap fdPayload (lookupFile "pic" $ files form)+-- @+class FromMultipart tag a where+ -- | Given a value of type 'MultipartData', which consists+ -- in a list of textual inputs and another list for+ -- files, try to extract a value of type @a@. When+ -- extraction fails, servant errors out with status code 400.+ fromMultipart :: MultipartData tag -> Either String a++instance FromMultipart tag (MultipartData tag) where+ fromMultipart = Right++-- | Allows you to tell servant how to turn a more structured type+-- into a 'MultipartData', which is what is actually sent by the+-- client.+--+-- @+-- data User = User { username :: Text, pic :: FilePath }+--+-- instance toMultipart Tmp User where+-- toMultipart user = MultipartData [Input "username" $ username user]+-- [FileData "pic"+-- (pic user)+-- "image/png"+-- (pic user)+-- ]+-- @+class ToMultipart tag a where+ -- | Given a value of type 'a', convert it to a+ -- 'MultipartData'.+ toMultipart :: a -> MultipartData tag++instance ToMultipart tag (MultipartData tag) where+ toMultipart = id++-- | Tag for data stored as a temporary file+data Tmp++-- | Tag for data stored in memory+data Mem++type family MultipartResult tag :: *+type instance MultipartResult Tmp = FilePath+type instance MultipartResult Mem = LBS.ByteString++instance HasLink sub => HasLink (MultipartForm tag a :> sub) where+#if MIN_VERSION_servant(0,14,0)+ type MkLink (MultipartForm tag a :> sub) r = MkLink sub r+ toLink toA _ = toLink toA (Proxy :: Proxy sub)+#else+ type MkLink (MultipartForm tag a :> sub) = MkLink sub+ toLink _ = toLink (Proxy :: Proxy sub)+#endif