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
|
import useSWR, { SWRResponse } from "swr"
import { RunnerApi, ExampleApi } from "slam-types"
import { useState } from "react"
const RUNNER_API_URL = String(import.meta.env.VITE_RUNNER_API_URL)
const EXAMPLE_API_URL = String(import.meta.env.VITE_EXAMPLE_API_URL)
const fetcher = (baseUrl: string) => (path: string) => fetch(`${baseUrl}${path}`)
.then(res => res.json())
.catch((err) => {
console.error(`An error occurred when fetching API path ${path}`)
console.error(err)
throw err
})
export interface APISuccess<D> {
readonly type: "success"
data: D
}
export interface APILoading {
readonly type: "loading"
}
export interface APIFailed<E> {
readonly type: "failed"
error: E
}
export interface APINotYetRequested {
readonly type: "not_yet_requested"
}
export type APIResult<D, E> = APISuccess<D> | APILoading | APIFailed<E> | APINotYetRequested
const mapSWRResult = <D, E>({ data, error }: SWRResponse<D, E>): APIResult<D, E> => {
if (error !== undefined) {
return { type: "failed", error }
}
const isLoading = data === undefined
if (isLoading) {
return { type: "loading" }
}
return { type: "success", data }
}
interface BasePostRequest<R> {
baseUrl: string
path: string
body: R
}
const postRequest = async <R, D, E>({ baseUrl, path, body }: BasePostRequest<R>, opts?: any): Promise<APIResult<D, E>> => {
const url = `${baseUrl}${path}`
const body_ = typeof body === "string" ? body : JSON.stringify(body)
try {
const resp = await fetch(url, { method: "POST", body: body_, headers: {
"Content-Type": "application/json",
}, ...opts })
if (resp.status >= 400) {
throw new Error(`${url} responded with ${resp.status}: ${resp.text}`)
}
const data = await resp.json()
return { type: "success", data }
} catch (err: any) {
console.error(`An error occurred when POSTing to API path ${path}`)
console.error(err)
return { type: "failed", error: err }
}
}
export const useExamples = () => mapSWRResult<ExampleApi.ListResponse.Body, Error>(
useSWR(ExampleApi.ListRequest.path, fetcher(EXAMPLE_API_URL)),
)
export type SubmitResult = APIResult<RunnerApi.SubmitResponse.Body["result"], Error>
const postSource = async (text: string): Promise<SubmitResult> => {
const apiResult = await postRequest<RunnerApi.SubmitRequest.Body, RunnerApi.SubmitResponse.Body, Error>(
{
baseUrl: RUNNER_API_URL,
path: RunnerApi.SubmitRequest.path,
body: {
source: text,
},
},
)
switch (apiResult.type) {
case "success":
return { type: "success", data: apiResult.data.result }
case "failed":
return { type: "failed", error: apiResult.error }
case "not_yet_requested":
case "loading":
return apiResult
}
}
export const useSubmitSource = () => {
const [result, setResult] = useState<SubmitResult>({ type: "not_yet_requested" })
const submitSource = async (text: string) => {
try {
setResult({ type: "loading" })
const apiResult = await postRequest<RunnerApi.SubmitRequest.Body, RunnerApi.SubmitResponse.Body, Error>(
{
baseUrl: RUNNER_API_URL,
path: RunnerApi.SubmitRequest.path,
body: {
source: text,
},
},
)
switch (apiResult.type) {
case "success":
setResult({ type: "success", data: apiResult.data.result })
break
case "failed":
setResult({ type: "failed", error: apiResult.error })
break
default:
break
}
} catch (err: any) {
setResult({ type: "failed", error: err })
}
}
return { result, submitSource }
}
|