improve protocol support

support verification after upload

support for errors embedded in json
This commit is contained in:
Joey Hess 2019-07-31 14:55:15 -04:00
parent 9040fea09e
commit f4e8ab969e
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38

154
Lfs.hs
View file

@ -27,18 +27,23 @@ instance FromJSON TransferRequest where
parseJSON = genericParseJSON transferRequestOptions parseJSON = genericParseJSON transferRequestOptions
transferRequestOptions :: Options transferRequestOptions :: Options
transferRequestOptions = nonNullOptions transferRequestOptions = stripFieldPrefix nonNullOptions
-- remove "req_"
{ fieldLabelModifier = drop 4 }
data TransferRequestObject = TransferRequestObject data TransferRequestObject = TransferRequestObject
{ oid :: SHA256 { req_oid :: SHA256
, size :: Integer , req_size :: Integer
} }
deriving (Generic, Show) deriving (Generic, Show)
instance ToJSON TransferRequestObject instance ToJSON TransferRequestObject where
instance FromJSON TransferRequestObject toJSON = genericToJSON transferRequestObjectOptions
toEncoding = genericToEncoding transferRequestObjectOptions
instance FromJSON TransferRequestObject where
parseJSON = genericParseJSON transferRequestObjectOptions
transferRequestObjectOptions :: Options
transferRequestObjectOptions = stripFieldPrefix defaultOptions
data TransferRequestOperation = RequestDownload | RequestUpload data TransferRequestOperation = RequestDownload | RequestUpload
deriving (Show) deriving (Show)
@ -64,18 +69,42 @@ instance IsTransferResponseOperation op => ToJSON (TransferResponse op) where
instance IsTransferResponseOperation op => FromJSON (TransferResponse op) instance IsTransferResponseOperation op => FromJSON (TransferResponse op)
-- | This is an error with a TransferRequest as a whole. It's also possible
-- for a TransferRequest to overall succeed, but fail for some
-- objects; such failures use TransferResponseObjectError.
data TransferResponseError = TransferResponseError data TransferResponseError = TransferResponseError
{ message :: T.Text { resperr_message :: T.Text
, request_id :: Maybe T.Text , resperr_request_id :: Maybe T.Text
, documentation_url :: Maybe Url , resperr_documentation_url :: Maybe Url
} }
deriving (Generic, Show) deriving (Generic, Show)
instance ToJSON TransferResponseError where instance ToJSON TransferResponseError where
toJSON = genericToJSON nonNullOptions toJSON = genericToJSON transferResponseErrorOptions
toEncoding = genericToEncoding nonNullOptions toEncoding = genericToEncoding transferResponseErrorOptions
instance FromJSON TransferResponseError instance FromJSON TransferResponseError where
parseJSON = genericParseJSON transferResponseErrorOptions
transferResponseErrorOptions :: Options
transferResponseErrorOptions = stripFieldPrefix nonNullOptions
-- | An error with a single object within a TransferRequest.
data TransferResponseObjectError = TransferResponseObjectError
{ respobjerr_code :: Int
, respobjerr_message :: T.Text
}
deriving (Generic, Show)
instance ToJSON TransferResponseObjectError where
toJSON = genericToJSON transferResponseObjectErrorOptions
toEncoding = genericToEncoding transferResponseObjectErrorOptions
instance FromJSON TransferResponseObjectError where
parseJSON = genericParseJSON transferResponseObjectErrorOptions
transferResponseObjectErrorOptions :: Options
transferResponseObjectErrorOptions = stripFieldPrefix nonNullOptions
data TransferAdapter = Basic data TransferAdapter = Basic
deriving (Show) deriving (Show)
@ -91,7 +120,8 @@ data TransferResponseOperation op = TransferResponseOperation
{ resp_oid :: SHA256 { resp_oid :: SHA256
, resp_size :: Integer , resp_size :: Integer
, resp_authenticated :: Maybe Bool , resp_authenticated :: Maybe Bool
, resp_actions :: op , resp_actions :: Maybe op
, resp_error :: Maybe TransferResponseObjectError
} }
deriving (Generic, Show) deriving (Generic, Show)
@ -103,9 +133,7 @@ instance FromJSON op => FromJSON (TransferResponseOperation op) where
parseJSON = genericParseJSON transferResponseOperationOptions parseJSON = genericParseJSON transferResponseOperationOptions
transferResponseOperationOptions :: Options transferResponseOperationOptions :: Options
transferResponseOperationOptions = nonNullOptions transferResponseOperationOptions = stripFieldPrefix nonNullOptions
-- remove "resp_"
{ fieldLabelModifier = drop 5 }
-- | Class of types that can be responses to a transfer request, -- | Class of types that can be responses to a transfer request,
-- that contain an operation to use to make the transfer. -- that contain an operation to use to make the transfer.
@ -120,12 +148,18 @@ instance ToJSON DownloadOperation
instance FromJSON DownloadOperation instance FromJSON DownloadOperation
data UploadOperation = UploadOperation data UploadOperation = UploadOperation
{ upload :: OperationParams } { upload :: OperationParams
, verify :: Maybe OperationParams
}
deriving (Generic, Show) deriving (Generic, Show)
instance IsTransferResponseOperation UploadOperation instance IsTransferResponseOperation UploadOperation
instance ToJSON UploadOperation where
toJSON = genericToJSON nonNullOptions
toEncoding = genericToEncoding nonNullOptions
instance FromJSON UploadOperation instance FromJSON UploadOperation
instance ToJSON UploadOperation
data OperationParams = OperationParams data OperationParams = OperationParams
{ href :: Url { href :: Url
@ -156,15 +190,19 @@ type SHA256 = T.Text
-- The input Request's url should be the discovered LFS endpoint. -- The input Request's url should be the discovered LFS endpoint.
-- Since this uses the LFS batch API, it adds /objects/batch to the end of -- Since this uses the LFS batch API, it adds /objects/batch to the end of
-- that url. -- that url.
transferRequest :: Request -> TransferRequest -> Request startTransferRequest :: Request -> TransferRequest -> Request
transferRequest r tr = r startTransferRequest r tr = addLfsJsonHeaders $ r
{ path = path r <> "/objects/batch" { path = path r <> "/objects/batch"
, requestHeaders = , method = "POST"
, requestBody = RequestBodyLBS (encode tr)
}
addLfsJsonHeaders :: Request -> Request
addLfsJsonHeaders r = r
{ requestHeaders =
[ ("Accept", lfsjson) [ ("Accept", lfsjson)
, ("Content-Type", lfsjson) , ("Content-Type", lfsjson)
] ]
, method = "POST"
, requestBody = RequestBodyLBS (encode tr)
} }
where where
lfsjson = "application/vnd.git-lfs+json" lfsjson = "application/vnd.git-lfs+json"
@ -186,28 +224,59 @@ parseResponseBody resp = case eitherDecode resp of
Left _ -> Left $ Left err Left _ -> Left $ Left err
Right tr -> Right tr Right tr -> Right tr
-- | Builds http requests that can be used to download the objects that -- | Builds a http request to perform a download.
-- were requested using a TransferRequest. downloadOperationRequest :: DownloadOperation -> Maybe Request
downloadRequests :: TransferResponse DownloadOperation -> ([(TransferResponseOperation DownloadOperation, Maybe Request)]) downloadOperationRequest = operationParamsRequest . download
downloadRequests = transferRequests download
-- | Builds http requests that can be used to upload objects. -- | Builds http request to perform an upload. The content to upload is
-- provided in the RequestBody, along with its SHA256 and size.
-- --
-- When the server already has an object, no request will be generated. -- If the LFS server requested verification, there will be a second
-- -- Request that does that; it should be run only after the upload has
-- The requestBody is not set here. When making a request, -- succeeded.
-- the content of the object needs to be provided as the body. uploadOperation :: UploadOperation -> RequestBody -> SHA256 -> Integer -> Maybe [Request]
uploadRequests :: TransferResponse UploadOperation -> ([(TransferResponseOperation UploadOperation, Maybe Request)]) uploadOperation op content oid size =
uploadRequests = transferRequests upload case (mkdlreq, mkverifyreq) of
(Nothing, _) -> Nothing
transferRequests :: (op -> OperationParams) -> TransferResponse op -> ([(TransferResponseOperation op, Maybe Request)]) (Just dlreq, Nothing) -> Just [dlreq]
transferRequests getps = map mkreq . objects (Just dlreq, Just verifyreq) -> Just [dlreq, verifyreq]
where where
mkreq op = (op, mkreq' (getps (resp_actions op))) mkdlreq = mkdlreq'
mkreq' ps = do <$> operationParamsRequest (upload op)
mkdlreq' r = r
{ method = "PUT"
, requestBody = content
}
mkverifyreq = mkverifyreq'
<$> (operationParamsRequest =<< verify op)
mkverifyreq' r = addLfsJsonHeaders $ r
{ method = "POST"
, requestBody = RequestBodyLBS $ encode $
VerifyBody oid size
}
data VerifyBody = VerifyBody
{ verifybody_oid :: SHA256
, verifybody_size :: Integer
}
deriving (Generic, Show)
instance ToJSON VerifyBody where
toJSON = genericToJSON verifyBodyOptions
toEncoding = genericToEncoding verifyBodyOptions
instance FromJSON VerifyBody where
parseJSON = genericParseJSON verifyBodyOptions
verifyBodyOptions :: Options
verifyBodyOptions = stripFieldPrefix defaultOptions
operationParamsRequest :: OperationParams -> Maybe Request
operationParamsRequest ps = do
r <- parseRequest (T.unpack (href ps)) r <- parseRequest (T.unpack (href ps))
let headers = map convheader $ maybe [] M.toList (header ps) let headers = map convheader $ maybe [] M.toList (header ps)
return $ r { requestHeaders = headers } return $ r { requestHeaders = headers }
where
convheader (k, v) = (CI.mk (E.encodeUtf8 k), E.encodeUtf8 v) convheader (k, v) = (CI.mk (E.encodeUtf8 k), E.encodeUtf8 v)
type Url = T.Text type Url = T.Text
@ -221,3 +290,8 @@ type HTTPHeaderValue = T.Text
-- Prevent Nothing from serializing to null. -- Prevent Nothing from serializing to null.
nonNullOptions :: Options nonNullOptions :: Options
nonNullOptions = defaultOptions { omitNothingFields = True } nonNullOptions = defaultOptions { omitNothingFields = True }
-- Remove prefix from field names.
stripFieldPrefix :: Options -> Options
stripFieldPrefix o =
o { fieldLabelModifier = drop 1 . dropWhile (/= '_') }