http://blah.com/blahs/132
HTTP
blah.com
/blahs/132
Example matrix from Wikipedia
Resource | GET | PUT | POST | DELETE |
---|---|---|---|---|
Collection http://eg.com/resources |
List the URIs and other details of elements. |
Replace the entire collection. |
Add a new entry to the collection. URI is assigned automatically. |
Delete the entire collection. |
Element http://eg.com/resources/item17 |
Retrieve addressed element expressed in appropriate media type |
Replace element or if it does not exist the create it. |
Not generally used. Treat element as collection and create new entry |
Delete the item from the collection. |
import Network.HTTP.Conduit
import Network.HTTP.Types.Header
import Network.Connection (TLSSettings (..))
import Network.Socket(withSocketsDo)
import qualified Data.ByteString.Lazy.Char8 as B
fetchTestIssue :: IO ()
fetchTestIssue = do
-- We use a demo instance of Jira available on the web
let _host = "https://jira.atlassian.com"
-- We use the latest version of REST API to select one of the issues
-- and we limit the returned fields to be the summary and description only
uri = _host ++ "/rest/api/latest/issue/DEMO-3083?fields=summary,description"
-- This is just to ignore the failure of the security certificate
settings = mkManagerSettings (TLSSettingsSimple True False False) Nothing
-- We make the request by parsing the URL, the request method by default is get
request <- parseUrl uri
-- We do the request and receive the response
response <- withSocketsDo $ withManagerSettings settings $ httpLbs request
-- We select some of the headers of the response
let hdr = filter g . responseHeaders $ response
g (h, _) | h == hContentType = True
g (h, _) | h == hServer = True
g _ = False
-- We get the response body
bdy = responseBody response
-- print the selected headers and response body
putStrLn $ "Response headers = \n" ++ show hdr
putStrLn "Response body = "
B.putStrLn bdy
Response headers =
[("Server","nginx"),("Content-Type","application/json;charset=UTF-8")]
Response body =
{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog",
"id":"333132",
"self":"https://jira.atlassian.com/rest/api/latest/issue/333132",
"key":"DEMO-3083",
"fields":{"summary":"Backspace to delete zero to enter your dosage ",
"description":"You have to delete zero first before you can put in your Dosage"}}
FromJSON
and ToJSON
instances for your type.
DeriveGeneric
GHC extensionnewtype
{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
import Network.HTTP.Conduit
import Network.Connection (TLSSettings (..))
import Network.Socket(withSocketsDo)
import Control.Applicative
import qualified Data.Aeson as AS
import Data.Aeson ((.:), (.:?), (.!=))
import qualified Data.Aeson.Types as AS (typeMismatch)
import qualified Data.Yaml as YAML
import qualified Data.ByteString.Char8 as B
import GHC.Generics
-- The data type that will represent our issue
data Issue = Issue {issueId :: Int, issueKey, issueSummary, issueDescription :: String}
deriving (Eq, Show, Read, Generic)
-- Automatically derive instances for our issue type allowing is to encode/decode
-- to and from JSON and YAML.
instance AS.ToJSON Issue
instance AS.FromJSON Issue
-- The newtype wrapper used to decode the JSON response body received
-- from the Jira server
newtype IssueResponse = IssueResponse {issueFromResponse :: Issue}
-- Manually define how to turn a JSON representation into a IssueResponse
instance AS.FromJSON IssueResponse where
parseJSON (AS.Object v) = do -- v is the parsed JSON object
fields <- v .: "fields" -- select the fields member
-- Lift the Issue constructor into the parsing monad and
-- apply it to the results of looking up values in the JSON object
Issue <$> (read <$> v .: "id") -- select id member as an Int
<*> v .: "key" -- select key member
<*> fields .: "summary" -- select summary from the fields
<*> fields .:? "description" -- optionally select description
-- from the fields.
.!= "No description" -- if it is not present then this
-- will be the default value
-- Wrap the result type in IssueResponse
>>= pure . IssueResponse
-- Error message on parse failure
parseJSON a = AS.typeMismatch "Expecting JSON object for Issue" a
fetchTestIssue :: IO ()
fetchTestIssue = do
-- We use a demo instance of Jira available on the web
let _host = "https://jira.atlassian.com"
-- We use the latest version of REST API to select one of the issues
-- and we limit the returned fields to be the summary and description only
uri = _host ++ "/rest/api/latest/issue/DEMO-3083?fields=summary,description"
-- This is just to ignore the failure of the security certificate
settings = mkManagerSettings (TLSSettingsSimple True False False) Nothing
-- We make the request by parsing the URL
request <- parseUrl uri
-- do the request
response <- withSocketsDo $ withManagerSettings settings $ httpLbs request
-- Get the response body.
-- Decode it as IssueResponse type possibly failing
-- If decoding was successful turn the result into an Issue type
-- Encode the whole result (possibly failed) as YAML
-- Print the resultant ByteString to the console
B.putStrLn . YAML.encode . fmap issueFromResponse . AS.decode . responseBody $ response
issueDescription: You have to delete zero first before you can put in your Dosage
issueId: 333132
issueKey: DEMO-3083
issueSummary: ! 'Backspace to delete zero to enter your dosage '
import Text.Pandoc
import Text.Pandoc.Builder hiding (space)
import Text.Blaze.Renderer.String
import qualified Data.Map as M
-- We use the helpers to construct an AST for a table with some text in it
aTable :: [Block]
aTable = toList $ -- convert the builder type to the AST type
-- Create a 2 column table without a caption a aligned left
table (str "") (replicate 2 (AlignLeft,0))
-- The header row for the table
[ para . str $ "Gordon", para . str $ "Ramsy"]
-- The rows of the table
[ [para . str $ "Sally", para . str $ "Storm"]
, [para . str $ "Blah", para . str $ "Bleh"]
]
-- Create our document along with its meta data
myDoc :: Pandoc
myDoc = Pandoc (Meta M.empty) aTable
main :: IO ()
main = do
-- render as Pandoc Markdown
putStrLn $ writeMarkdown def myDoc
-- render as HTML
putStrLn $ renderMarkup $ writeHtml def myDoc
Gordon Ramsy
-------- -------
Sally Storm
Blah Bleh
:
<table>
<caption></caption>
<thead>
<tr class="header">
<th align="left"><p>Gordon</p></th>
<th align="left"><p>Ramsy</p></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td align="left"><p>Sally</p></td>
<td align="left"><p>Storm</p></td>
</tr>
<tr class="even">
<td align="left"><p>Blah</p></td>
<td align="left"><p>Bleh</p></td>
</tr>
</tbody>
import Text.Parsec.Char
import Text.Parsec.String (Parser)
import Text.Parsec.Prim hiding ((<|>))
import Text.Parsec.Combinator
import Control.Applicative
import Control.Monad
import qualified Data.Map as M
import Data.Maybe
-- Parse an issue description and replace the issue references
replaceIssueRefs :: M.Map String String -> Parser String
-- multiple times parse either a link or normal text
-- and then concatenate it all into a single string
replaceIssueRefs m = concat <$> (many1 . choice $ [issue_ref, normal_text])
where
-- normal text is any character until we reach an issue reference or end of file
normal_text = manyTill anyChar (lookAhead (void issue_ref <|> eof))
-- check that this parse does not except empty text
>>= \txt' -> if null txt' then fail "" else return txt'
-- match the string DEMO- followed by at least 1 digit
-- lookup the matched string in the map replacing it
-- if the parser fails consume no input
issue_ref = try $ fromJust . (`M.lookup` m) <$> ((++) <$> string "DEMO-" <*> many1 digit)
main :: IO ()
main = do
-- The map of issue references to replace
let m = M.fromList [("DEMO-132", "OMED-457"), ("DEMO-987", "OMED-765")]
-- The input issue description text
s = "See issue DEMO-132 for more information related the bug listed in DEMO-987"
-- parse the issue description using the replaceIssueRefs parser
case parse (replaceIssueRefs m) "" s of
Left e -> print e -- on failure print error
Right rs -> putStrLn rs -- on success print out:
-- See issue OMED-457 for more information related the bug listed in OMED-765
printf
and debugger)Debug.Trace
? No still difficult. Lazy and declarative.Debug.Trace
is a printf
escape hatch for pure code-- ......
type MyParser = Parsec String ParseState
-- .....
-- Really gross but worked.
-- Force trace to emit by requiring subsequent parser actions
-- to access the parse state through my trace message
traceM' :: String -> MyParser ()
traceM' msg = getState >>= (\s -> return $! trace ('\n' : msg) s) >>= setState