jsfetch.nim 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. ## - Fetch for the JavaScript target: https://developer.mozilla.org/docs/Web/API/Fetch_API
  2. when not defined(js):
  3. {.fatal: "Module jsfetch is designed to be used with the JavaScript backend.".}
  4. import std/[asyncjs, jsformdata, jsheaders]
  5. export jsformdata, jsheaders
  6. from std/httpcore import HttpMethod
  7. from std/jsffi import JsObject
  8. type
  9. FetchOptions* = ref object of JsRoot ## Options for Fetch API.
  10. keepalive*: bool
  11. metod* {.importjs: "method".}: cstring
  12. body*, integrity*, referrer*, mode*, credentials*, cache*, redirect*, referrerPolicy*: cstring
  13. headers*: Headers
  14. FetchModes* = enum ## Mode options.
  15. fmCors = "cors"
  16. fmNoCors = "no-cors"
  17. fmSameOrigin = "same-origin"
  18. FetchCredentials* = enum ## Credential options. See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
  19. fcInclude = "include"
  20. fcSameOrigin = "same-origin"
  21. fcOmit = "omit"
  22. FetchCaches* = enum ## https://developer.mozilla.org/docs/Web/API/Request/cache
  23. fchDefault = "default"
  24. fchNoStore = "no-store"
  25. fchReload = "reload"
  26. fchNoCache = "no-cache"
  27. fchForceCache = "force-cache"
  28. FetchRedirects* = enum ## Redirects options.
  29. frFollow = "follow"
  30. frError = "error"
  31. frManual = "manual"
  32. FetchReferrerPolicies* = enum ## Referrer Policy options.
  33. frpNoReferrer = "no-referrer"
  34. frpNoReferrerWhenDowngrade = "no-referrer-when-downgrade"
  35. frpOrigin = "origin"
  36. frpOriginWhenCrossOrigin = "origin-when-cross-origin"
  37. frpUnsafeUrl = "unsafe-url"
  38. Response* = ref object of JsRoot ## https://developer.mozilla.org/en-US/docs/Web/API/Response
  39. bodyUsed*, ok*, redirected*: bool
  40. typ* {.importjs: "type".}: cstring
  41. url*, statusText*: cstring
  42. status*: cint
  43. headers*: Headers
  44. body*: cstring
  45. Request* = ref object of JsRoot ## https://developer.mozilla.org/en-US/docs/Web/API/Request
  46. bodyUsed*, ok*, redirected*: bool
  47. typ* {.importjs: "type".}: cstring
  48. url*, statusText*: cstring
  49. status*: cint
  50. headers*: Headers
  51. body*: cstring
  52. func newResponse*(body: cstring | FormData): Response {.importjs: "(new Response(#))".}
  53. ## Constructor for `Response`. This does *not* call `fetch()`. Same as `new Response()`.
  54. func newRequest*(url: cstring): Request {.importjs: "(new Request(#))".}
  55. ## Constructor for `Request`. This does *not* call `fetch()`. Same as `new Request()`.
  56. func newRequest*(url: cstring; fetchOptions: FetchOptions): Request {.importjs: "(new Request(#, #))".}
  57. ## Constructor for `Request` with `fetchOptions`. Same as `fetch(url, fetchOptions)`.
  58. func clone*(self: Response | Request): Response {.importjs: "#.$1()".}
  59. ## https://developer.mozilla.org/en-US/docs/Web/API/Response/clone
  60. proc text*(self: Response): Future[cstring] {.importjs: "#.$1()".}
  61. ## https://developer.mozilla.org/en-US/docs/Web/API/Response/text
  62. proc json*(self: Response): Future[JsObject] {.importjs: "#.$1()".}
  63. ## https://developer.mozilla.org/en-US/docs/Web/API/Response/json
  64. proc formData*(self: Response): Future[FormData] {.importjs: "#.$1()".}
  65. ## https://developer.mozilla.org/en-US/docs/Web/API/Response/formData
  66. proc unsafeNewFetchOptions*(metod, body, mode, credentials, cache, referrerPolicy: cstring;
  67. keepalive: bool; redirect = "follow".cstring; referrer = "client".cstring; integrity = "".cstring; headers: Headers = newHeaders()): FetchOptions {.importjs:
  68. "{method: #, body: #, mode: #, credentials: #, cache: #, referrerPolicy: #, keepalive: #, redirect: #, referrer: #, integrity: #, headers: #}".}
  69. ## .. warning:: Unsafe `newfetchOptions`.
  70. func newfetchOptions*(metod = HttpGet; body: cstring = nil;
  71. mode = fmCors; credentials = fcSameOrigin; cache = fchDefault; referrerPolicy = frpNoReferrerWhenDowngrade;
  72. keepalive = false; redirect = frFollow; referrer = "client".cstring; integrity = "".cstring,
  73. headers: Headers = newHeaders()): FetchOptions =
  74. ## Constructor for `FetchOptions`.
  75. result = FetchOptions(
  76. body: if metod notin {HttpHead, HttpGet}: body else: nil,
  77. mode: cstring($mode), credentials: cstring($credentials), cache: cstring($cache), referrerPolicy: cstring($referrerPolicy),
  78. keepalive: keepalive, redirect: cstring($redirect), referrer: referrer, integrity: integrity, headers: headers,
  79. metod: (case metod
  80. of HttpHead: "HEAD".cstring
  81. of HttpGet: "GET".cstring
  82. of HttpPost: "POST".cstring
  83. of HttpPut: "PUT".cstring
  84. of HttpDelete: "DELETE".cstring
  85. of HttpPatch: "PATCH".cstring
  86. else: "GET".cstring
  87. )
  88. )
  89. proc fetch*(url: cstring | Request): Future[Response] {.importjs: "$1(#)".}
  90. ## `fetch()` API, simple `GET` only, returns a `Future[Response]`.
  91. proc fetch*(url: cstring | Request; options: FetchOptions): Future[Response] {.importjs: "$1(#, #)".}
  92. ## `fetch()` API that takes a `FetchOptions`, returns a `Future[Response]`.
  93. func toCstring*(self: Request | Response | FetchOptions): cstring {.importjs: "JSON.stringify(#)".}
  94. func `$`*(self: Request | Response | FetchOptions): string = $toCstring(self)
  95. runnableExamples("-r:off"):
  96. import std/[asyncjs, jsconsole, jsformdata, jsheaders]
  97. from std/httpcore import HttpMethod
  98. from std/jsffi import JsObject
  99. from std/sugar import `=>`
  100. block:
  101. let options0: FetchOptions = unsafeNewFetchOptions(
  102. metod = "POST".cstring,
  103. body = """{"key": "value"}""".cstring,
  104. mode = "no-cors".cstring,
  105. credentials = "omit".cstring,
  106. cache = "no-cache".cstring,
  107. referrerPolicy = "no-referrer".cstring,
  108. keepalive = false,
  109. redirect = "follow".cstring,
  110. referrer = "client".cstring,
  111. integrity = "".cstring,
  112. headers = newHeaders()
  113. )
  114. assert options0.keepalive == false
  115. assert options0.metod == "POST".cstring
  116. assert options0.body == """{"key": "value"}""".cstring
  117. assert options0.mode == "no-cors".cstring
  118. assert options0.credentials == "omit".cstring
  119. assert options0.cache == "no-cache".cstring
  120. assert options0.referrerPolicy == "no-referrer".cstring
  121. assert options0.redirect == "follow".cstring
  122. assert options0.referrer == "client".cstring
  123. assert options0.integrity == "".cstring
  124. assert options0.headers.len == 0
  125. block:
  126. let options1: FetchOptions = newFetchOptions(
  127. metod = HttpPost,
  128. body = """{"key": "value"}""".cstring,
  129. mode = fmNoCors,
  130. credentials = fcOmit,
  131. cache = fchNoCache,
  132. referrerPolicy = frpNoReferrer,
  133. keepalive = false,
  134. redirect = frFollow,
  135. referrer = "client".cstring,
  136. integrity = "".cstring,
  137. headers = newHeaders()
  138. )
  139. assert options1.keepalive == false
  140. assert options1.metod == $HttpPost
  141. assert options1.body == """{"key": "value"}""".cstring
  142. assert options1.mode == $fmNoCors
  143. assert options1.credentials == $fcOmit
  144. assert options1.cache == $fchNoCache
  145. assert options1.referrerPolicy == $frpNoReferrer
  146. assert options1.redirect == $frFollow
  147. assert options1.referrer == "client".cstring
  148. assert options1.integrity == "".cstring
  149. assert options1.headers.len == 0
  150. block:
  151. let response: Response = newResponse(body = "-. .. --".cstring)
  152. let request: Request = newRequest(url = "http://nim-lang.org".cstring)
  153. if not defined(nodejs):
  154. block:
  155. proc doFetch(): Future[Response] {.async.} =
  156. fetch "https://httpbin.org/get".cstring
  157. proc example() {.async.} =
  158. let response: Response = await doFetch()
  159. assert response.ok
  160. assert response.status == 200.cint
  161. assert response.headers is Headers
  162. assert response.body is cstring
  163. discard example()
  164. block:
  165. proc example2 {.async.} =
  166. await fetch("https://api.github.com/users/torvalds".cstring)
  167. .then((response: Response) => response.json())
  168. .then((json: JsObject) => console.log(json))
  169. .catch((err: Error) => console.log("Request Failed", err))
  170. discard example2()