jsfetch.nim 7.8 KB

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