summary refs log tree commit diff stats
path: root/lib/std/jsfetch.nim
blob: 21959461973d494ca68991c2b9f8cb48e865b455 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
## - Fetch for the JavaScript target: https://developer.mozilla.org/docs/Web/API/Fetch_API
when not defined(js):
  {.fatal: "Module jsfetch is designed to be used with the JavaScript backend.".}

import std/[asyncjs, jsformdata, jsheaders]
export jsformdata, jsheaders
from std/httpcore import HttpMethod
from std/jsffi import JsObject

type
  FetchOptions* = ref object of JsRoot  ## Options for Fetch API.
    keepalive*: bool
    metod* {.importjs: "method".}: cstring
    body*, integrity*, referrer*, mode*, credentials*, cache*, redirect*, referrerPolicy*: cstring
    headers*: Headers

  FetchModes* = enum  ## Mode options.
    fmCors = "cors"
    fmNoCors = "no-cors"
    fmSameOrigin = "same-origin"

  FetchCredentials* = enum  ## Credential options. See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
    fcInclude = "include"
    fcSameOrigin = "same-origin"
    fcOmit = "omit"

  FetchCaches* = enum  ## https://developer.mozilla.org/docs/Web/API/Request/cache
    fchDefault = "default"
    fchNoStore = "no-store"
    fchReload = "reload"
    fchNoCache = "no-cache"
    fchForceCache = "force-cache"

  FetchRedirects* = enum  ## Redirects options.
    frFollow = "follow"
    frError = "error"
    frManual = "manual"

  FetchReferrerPolicies* = enum  ## Referrer Policy options.
    frpNoReferrer = "no-referrer"
    frpNoReferrerWhenDowngrade = "no-referrer-when-downgrade"
    frpOrigin = "origin"
    frpOriginWhenCrossOrigin = "origin-when-cross-origin"
    frpUnsafeUrl = "unsafe-url"

  Response* = ref object of JsRoot  ## https://developer.mozilla.org/en-US/docs/Web/API/Response
    bodyUsed*, ok*, redirected*: bool
    typ* {.importjs: "type".}: cstring
    url*, statusText*: cstring
    status*: cint
    headers*: Headers
    body*: cstring

  Request* = ref object of JsRoot  ## https://developer.mozilla.org/en-US/docs/Web/API/Request
    bodyUsed*, ok*, redirected*: bool
    typ* {.importjs: "type".}: cstring
    url*, statusText*: cstring
    status*: cint
    headers*: Headers
    body*: cstring

func newResponse*(body: cstring | FormData): Response {.importjs: "(new Response(#))".}
  ## Constructor for `Response`. This does *not* call `fetch()`. Same as `new Response()`.

func newRequest*(url: cstring): Request {.importjs: "(new Request(#))".}
  ## Constructor for `Request`. This does *not* call `fetch()`. Same as `new Request()`.

func newRequest*(url: cstring; fetchOptions: FetchOptions): Request {.importjs: "(new Request(#, #))".}
  ## Constructor for `Request` with `fetchOptions`. Same as `fetch(url, fetchOptions)`.

func clone*(self: Response | Request): Response {.importjs: "#.$1()".}
  ## https://developer.mozilla.org/en-US/docs/Web/API/Response/clone

proc text*(self: Response): Future[cstring] {.importjs: "#.$1()".}
  ## https://developer.mozilla.org/en-US/docs/Web/API/Response/text

proc json*(self: Response): Future[JsObject] {.importjs: "#.$1()".}
  ## https://developer.mozilla.org/en-US/docs/Web/API/Response/json

proc formData*(self: Response): Future[FormData] {.importjs: "#.$1()".}
  ## https://developer.mozilla.org/en-US/docs/Web/API/Response/formData

proc unsafeNewFetchOptions*(metod, body, mode, credentials, cache, referrerPolicy: cstring;
    keepalive: bool; redirect = "follow".cstring; referrer = "client".cstring; integrity = "".cstring; headers: Headers = newHeaders()): FetchOptions {.importjs:
    "{method: #, body: #, mode: #, credentials: #, cache: #, referrerPolicy: #, keepalive: #, redirect: #, referrer: #, integrity: #, headers: #}".}
  ## .. warning:: Unsafe `newfetchOptions`.

func newfetchOptions*(metod = HttpGet; body: cstring = nil;
    mode = fmCors; credentials = fcSameOrigin; cache = fchDefault; referrerPolicy = frpNoReferrerWhenDowngrade;
    keepalive = false; redirect = frFollow; referrer = "client".cstring; integrity = "".cstring,
    headers: Headers = newHeaders()): FetchOptions =
  ## Constructor for `FetchOptions`.
  result = FetchOptions(
    body: if metod notin {HttpHead, HttpGet}: body else: nil, 
    mode: cstring($mode), credentials: cstring($credentials), cache: cstring($cache), referrerPolicy: cstring($referrerPolicy),
    keepalive: keepalive, redirect: cstring($redirect), referrer: referrer, integrity: integrity, headers: headers,
    metod: (case metod
      of HttpHead:   "HEAD".cstring
      of HttpGet:    "GET".cstring
      of HttpPost:   "POST".cstring
      of HttpPut:    "PUT".cstring
      of HttpDelete: "DELETE".cstring
      of HttpPatch:  "PATCH".cstring
      else:          "GET".cstring
    )
  )

proc fetch*(url: cstring | Request): Future[Response] {.importjs: "$1(#)".}
  ## `fetch()` API, simple `GET` only, returns a `Future[Response]`.

proc fetch*(url: cstring | Request; options: FetchOptions): Future[Response] {.importjs: "$1(#, #)".}
  ## `fetch()` API that takes a `FetchOptions`, returns a `Future[Response]`.

func toCstring*(self: Request | Response | FetchOptions): cstring {.importjs: "JSON.stringify(#)".}

func `$`*(self: Request | Response | FetchOptions): string = $toCstring(self)


runnableExamples("-r:off"):
  import std/[asyncjs, jsconsole, jsformdata, jsheaders]
  from std/httpcore import HttpMethod
  from std/jsffi import JsObject
  from std/sugar import `=>`

  block:
    let options0: FetchOptions = unsafeNewFetchOptions(
      metod = "POST".cstring,
      body = """{"key": "value"}""".cstring,
      mode = "no-cors".cstring,
      credentials = "omit".cstring,
      cache = "no-cache".cstring,
      referrerPolicy = "no-referrer".cstring,
      keepalive = false,
      redirect = "follow".cstring,
      referrer = "client".cstring,
      integrity = "".cstring,
      headers = newHeaders()
    )
    assert options0.keepalive == false
    assert options0.metod == "POST".cstring
    assert options0.body == """{"key": "value"}""".cstring
    assert options0.mode == "no-cors".cstring
    assert options0.credentials == "omit".cstring
    assert options0.cache == "no-cache".cstring
    assert options0.referrerPolicy == "no-referrer".cstring
    assert options0.redirect == "follow".cstring
    assert options0.referrer == "client".cstring
    assert options0.integrity == "".cstring
    assert options0.headers.len == 0

  block:
    let options1: FetchOptions = newFetchOptions(
      metod =  HttpPost,
      body = """{"key": "value"}""".cstring,
      mode = fmNoCors,
      credentials = fcOmit,
      cache = fchNoCache,
      referrerPolicy = frpNoReferrer,
      keepalive = false,
      redirect = frFollow,
      referrer = "client".cstring,
      integrity = "".cstring,
      headers = newHeaders()
    )
    assert options1.keepalive == false
    assert options1.metod == $HttpPost
    assert options1.body == """{"key": "value"}""".cstring
    assert options1.mode == $fmNoCors
    assert options1.credentials == $fcOmit
    assert options1.cache == $fchNoCache
    assert options1.referrerPolicy == $frpNoReferrer
    assert options1.redirect == $frFollow
    assert options1.referrer == "client".cstring
    assert options1.integrity == "".cstring
    assert options1.headers.len == 0

  block:
    let response: Response = newResponse(body = "-. .. --".cstring)
    let request: Request = newRequest(url = "http://nim-lang.org".cstring)

  if not defined(nodejs):
    block:
      proc doFetch(): Future[Response] {.async.} =
        fetch "https://httpbin.org/get".cstring

      proc example() {.async.} =
        let response: Response = await doFetch()
        assert response.ok
        assert response.status == 200.cint
        assert response.headers is Headers
        assert response.body is cstring

      discard example()

    block:
      proc example2 {.async.} =
        await fetch("https://api.github.com/users/torvalds".cstring)
          .then((response: Response) => response.json())
          .then((json: JsObject) => console.log(json))
          .catch((err: Error) => console.log("Request Failed", err))

      discard example2()