Skip to content

Instantly share code, notes, and snippets.

@bslatner
Last active October 31, 2022 15:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bslatner/4dbc9d7f892560cc6e6ad54a44ad9113 to your computer and use it in GitHub Desktop.
Save bslatner/4dbc9d7f892560cc6e6ad54a44ad9113 to your computer and use it in GitHub Desktop.
Code for making rest API requests in F# using the RestSharp library
// StartResult would be the type of the response. Must be marked with [<CLIMutable>]
let response =
restWithResponse<StartResult> (
POST >> toResource "stopwatch/{type}/{key}/start" >> atUrl config.Url
>> withUrlSegment "type" stopwatchType
>> withUrlSegment "key" key
>> withFormValue "owner" owner
>> withExpectedStatusOk
)
module RestClient
open System.Net
open RestSharp
type RestApiMethod = GET | POST | PUT | DELETE
type RestHttpStatusCodeExpectation =
| Exact of HttpStatusCode
| Several of HttpStatusCode list
| Range of HttpStatusCode * HttpStatusCode
type RestApiBody =
| JsonString of string
| JsonObject of obj
| Xml of string
| XmlWithNamespace of string * string
type RestApiRequest =
{
Url : string option
Resource : string option
Method : RestApiMethod option
Body : RestApiBody option
QueryStrings : (string * string) list option
FormValues : (string * string) list option
UrlSegments : (string * string) list option
Headers : (string * string) list option
Cookies : (string * string) list option
ExpectedStatusCode : RestHttpStatusCodeExpectation option
}
let newRestRequest =
{
Url = None
Resource = None
Method = None
Body = None
QueryStrings = None
FormValues = None
UrlSegments = None
Headers = None
Cookies = None
ExpectedStatusCode = None
}
let addToKvpList (kvpList : (string * string) list option) k v =
match kvpList with
| Some l -> Some ((k,v)::l)
| None -> Some [(k,v)]
let GET request =
{ request with Method = Some RestApiMethod.GET }
let POST request =
{ request with Method = Some RestApiMethod.POST }
let PUT request =
{ request with Method = Some RestApiMethod.PUT }
let DELETE request =
{ request with Method = Some RestApiMethod.DELETE }
let setUrl url request = { request with Url = Some url }
let fromUrl = setUrl
let atUrl = setUrl
let setResource resource request = { request with Resource = Some resource }
let theResource = setResource
let toResource = setResource
let withJsonStringBody body request =
{ request with Body = Some (JsonString(body)) }
let withJsonObjectBody body request =
{ request with Body = Some (JsonObject(body)) }
let withXmlBody body request =
{ request with Body = Some (Xml(body)) }
let withXmlBodyWithNamespace body ns request =
{ request with Body = Some (XmlWithNamespace(body,ns)) }
let withQueryString key value request =
{ request with QueryStrings = addToKvpList request.QueryStrings key value }
let withFormValue key value request =
{ request with FormValues = addToKvpList request.FormValues key value }
let withUrlSegment key value request =
{ request with UrlSegments = addToKvpList request.UrlSegments key value }
let withHeader key value request =
{ request with Headers = addToKvpList request.Headers key value }
let withCookie key value request =
{ request with Cookies = addToKvpList request.Cookies key value }
let withExpectedStatus (code : HttpStatusCode) request =
{ request with ExpectedStatusCode = Some (Exact(code)) }
let withExpectedStatuses (codes : seq<HttpStatusCode>) request =
{ request with ExpectedStatusCode = Some (Several(codes |> List.ofSeq)) }
let withExpectedStatusRange (code1 : HttpStatusCode) (code2 : HttpStatusCode) request =
{ request with ExpectedStatusCode = Some (Range(code1,code2)) }
let withExpectedStatusOk request =
request |> withExpectedStatus HttpStatusCode.OK
let buildRestSharpRequest request =
let translateMethod = function
| GET -> Method.GET
| POST -> Method.POST
| PUT -> Method.PUT
| DELETE -> Method.DELETE
let addListValues (f : string * string -> IRestRequest) (l : (string * string) list option) =
match l with
| None -> ()
| Some l ->
for k,v in l do
f(k,v) |> ignore
let client =
match request.Url with
| Some url -> RestClient(url)
| _ -> failwith "No URL specified"
let restRequest =
match request.Method,request.Resource with
| Some mthd,Some res -> RestRequest(res, translateMethod mthd)
| _ -> failwith "Missing method or resource"
match request.Body with
| None -> ()
| Some body ->
match body with
| JsonString s -> restRequest.AddJsonBody(s) |> ignore
| JsonObject o -> restRequest.AddJsonBody(o) |> ignore
| Xml x -> restRequest.AddXmlBody(x) |> ignore
| XmlWithNamespace (x,ns) -> restRequest.AddXmlBody(x,ns) |> ignore
request.QueryStrings |> addListValues restRequest.AddQueryParameter
request.FormValues |> addListValues restRequest.AddParameter
request.UrlSegments |> addListValues restRequest.AddUrlSegment
request.Headers |> addListValues restRequest.AddHeader
request.Cookies |> addListValues restRequest.AddCookie
client,restRequest
let evalRestSharpResponse req (rsp : IRestResponse) =
let goodStatusCode =
match req.ExpectedStatusCode with
| None -> true
| Some expectation ->
match expectation with
| Exact exact -> exact = rsp.StatusCode
| Several several -> several |> List.contains rsp.StatusCode
| Range (l,h) -> rsp.StatusCode >= l && rsp.StatusCode <= h
if not goodStatusCode then failwith (sprintf "Unexpected HTTP status code: %A" rsp.StatusCode)
if not (isNull rsp.ErrorException) then
raise rsp.ErrorException
elif not (System.String.IsNullOrWhiteSpace(rsp.ErrorMessage)) then
failwith (sprintf "Error returned by client: %s" rsp.ErrorMessage)
let rest (f : RestApiRequest -> RestApiRequest) =
let request = f newRestRequest
let client,restRequest = buildRestSharpRequest request
let restResponse = client.Execute(restRequest)
evalRestSharpResponse request restResponse
let restWithResponse<'a when 'a : ( new : unit -> 'a)> (f : RestApiRequest -> RestApiRequest) : 'a =
let request = f newRestRequest
let client,restRequest = buildRestSharpRequest request
let restResponse = client.Execute<'a>(restRequest)
evalRestSharpResponse request restResponse
restResponse.Data
let restAsync (f : RestApiRequest -> RestApiRequest) = async {
let request = f newRestRequest
let client,restRequest = buildRestSharpRequest request
let! restResponse = Async.AwaitTask(client.ExecuteTaskAsync(restRequest))
evalRestSharpResponse request restResponse
}
let restWithResponseAsync<'a when 'a : ( new : unit -> 'a)> (f : RestApiRequest -> RestApiRequest) : Async<'a> = async {
let request = f newRestRequest
let client,restRequest = buildRestSharpRequest request
let! restResponse = Async.AwaitTask(client.ExecuteTaskAsync<'a>(restRequest))
evalRestSharpResponse request restResponse
return restResponse.Data
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment