Skip to content

Commit 8f6fa1a

Browse files
authored
Merge pull request #1011 from rust-lang/api-smoothing
Simplify API interactions from the frontend
2 parents 7b99a67 + e4f6e0b commit 8f6fa1a

17 files changed

+93
-180
lines changed

ui/frontend/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ module.exports = {
6767
'Header.tsx',
6868
'PopButton.tsx',
6969
'Stdin.tsx',
70+
'api.ts',
7071
'compileActions.ts',
7172
'editor/AceEditor.tsx',
7273
'editor/SimpleEditor.tsx',

ui/frontend/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ node_modules
1818
!Header.tsx
1919
!PopButton.tsx
2020
!Stdin.tsx
21+
!api.ts
2122
!compileActions.ts
2223
!editor/AceEditor.tsx
2324
!editor/SimpleEditor.tsx

ui/frontend/actions.ts

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import fetch from 'isomorphic-fetch';
21
import { ThunkAction as ReduxThunkAction, AnyAction } from '@reduxjs/toolkit';
32

43
import {
@@ -35,21 +34,6 @@ import { performCompileToLlvmIrOnly } from './reducers/output/llvmIr';
3534
import { performCompileToMirOnly } from './reducers/output/mir';
3635
import { performCompileToWasmOnly } from './reducers/output/wasm';
3736

38-
export const routes = {
39-
compile: '/compile',
40-
execute: '/execute',
41-
format: '/format',
42-
clippy: '/clippy',
43-
miri: '/miri',
44-
macroExpansion: '/macro-expansion',
45-
meta: {
46-
crates: '/meta/crates',
47-
versions: '/meta/versions',
48-
gistSave: '/meta/gist',
49-
gistLoad: '/meta/gist/id',
50-
},
51-
};
52-
5337
export type ThunkAction<T = void> = ReduxThunkAction<T, State, {}, Action>;
5438
export type SimpleThunkAction<T = void> = ReduxThunkAction<T, State, {}, AnyAction>;
5539

@@ -148,87 +132,6 @@ export const reExecuteWithBacktrace = (): ThunkAction => dispatch => {
148132
dispatch(performExecuteOnly());
149133
};
150134

151-
type FetchArg = Parameters<typeof fetch>[0];
152-
153-
export function jsonGet(url: FetchArg): Promise<unknown> {
154-
return fetchJson(url, {
155-
method: 'get',
156-
});
157-
}
158-
159-
export function jsonPost(url: FetchArg, body: Record<string, any>): Promise<unknown> {
160-
return fetchJson(url, {
161-
method: 'post',
162-
body: JSON.stringify(body),
163-
});
164-
}
165-
166-
async function fetchJson(url: FetchArg, args: RequestInit) {
167-
const headers = new Headers(args.headers);
168-
headers.set('Content-Type', 'application/json');
169-
170-
let response;
171-
try {
172-
response = await fetch(url, { ...args, headers });
173-
} catch (networkError) {
174-
// e.g. server unreachable
175-
if (networkError instanceof Error) {
176-
throw ({
177-
error: `Network error: ${networkError.toString()}`,
178-
});
179-
} else {
180-
throw ({
181-
error: 'Unknown error while fetching JSON',
182-
});
183-
}
184-
}
185-
186-
let body;
187-
try {
188-
body = await response.json();
189-
} catch (convertError) {
190-
if (convertError instanceof Error) {
191-
throw ({
192-
error: `Response was not JSON: ${convertError.toString()}`,
193-
});
194-
} else {
195-
throw ({
196-
error: 'Unknown error while converting JSON',
197-
});
198-
}
199-
}
200-
201-
if (response.ok) {
202-
// HTTP 2xx
203-
return body;
204-
} else {
205-
// HTTP 4xx, 5xx (e.g. malformed JSON request)
206-
throw body;
207-
}
208-
}
209-
210-
// We made some strange decisions with how the `fetchJson` function
211-
// communicates errors, so we untwist those here to fit better with
212-
// redux-toolkit's ideas.
213-
export const adaptFetchError = async <R>(cb: () => Promise<R>): Promise<R> => {
214-
let result;
215-
216-
try {
217-
result = await cb();
218-
} catch (e) {
219-
if (e && typeof e === 'object' && 'error' in e && typeof e.error === 'string') {
220-
throw new Error(e.error);
221-
} else {
222-
throw new Error('An unknown error occurred');
223-
}
224-
}
225-
226-
if (result && typeof result === 'object' && 'error' in result && typeof result.error === 'string') {
227-
throw new Error(result.error);
228-
}
229-
230-
return result;
231-
}
232135

233136
function performAutoOnly(): ThunkAction {
234137
return function(dispatch, getState) {

ui/frontend/api.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as z from 'zod';
2+
3+
export const routes = {
4+
compile: '/compile',
5+
execute: '/execute',
6+
format: '/format',
7+
clippy: '/clippy',
8+
miri: '/miri',
9+
macroExpansion: '/macro-expansion',
10+
meta: {
11+
crates: '/meta/crates',
12+
versions: '/meta/versions',
13+
gistSave: '/meta/gist',
14+
gistLoad: '/meta/gist/id',
15+
},
16+
};
17+
18+
type FetchArg = Parameters<typeof fetch>[0];
19+
20+
export function jsonGet(url: FetchArg): Promise<unknown> {
21+
return fetchJson(url, {
22+
method: 'get',
23+
});
24+
}
25+
26+
export function jsonPost(url: FetchArg, body: Record<string, any>): Promise<unknown> {
27+
return fetchJson(url, {
28+
method: 'post',
29+
body: JSON.stringify(body),
30+
});
31+
}
32+
33+
const ErrorResponse = z.object({
34+
error: z.string(),
35+
});
36+
type ErrorResponse = z.infer<typeof ErrorResponse>;
37+
38+
async function fetchJson(url: FetchArg, args: RequestInit) {
39+
const headers = new Headers(args.headers);
40+
headers.set('Content-Type', 'application/json');
41+
42+
const response = await fetch(url, { ...args, headers });
43+
const body = await response.json();
44+
45+
if (response.ok) {
46+
// HTTP 2xx
47+
return body;
48+
} else {
49+
// HTTP 4xx, 5xx (e.g. malformed JSON request)
50+
const error = await ErrorResponse.parseAsync(body);
51+
throw new Error(`The server reported an error: ${error.error}`);
52+
}
53+
}

ui/frontend/compileActions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { AsyncThunk, createAsyncThunk } from '@reduxjs/toolkit';
22
import * as z from 'zod';
33

4-
import { SimpleThunkAction, adaptFetchError, jsonPost, routes } from './actions';
4+
import { SimpleThunkAction } from './actions';
5+
import { jsonPost, routes } from './api';
56
import { compileRequestPayloadSelector } from './selectors';
67

78
interface CompileRequestBody {
@@ -37,7 +38,7 @@ interface CompileActions {
3738

3839
export const makeCompileActions = ({ sliceName, target }: Props): CompileActions => {
3940
const action = createAsyncThunk(sliceName, async (payload: CompileRequestBody) => {
40-
const d = await adaptFetchError(() => jsonPost(routes.compile, payload));
41+
const d = await jsonPost(routes.compile, payload);
4142
return CompileResponseBody.parseAsync(d);
4243
});
4344

ui/frontend/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
"core-js": "^3.1.3",
1212
"history": "^5.3.0",
1313
"immer": "^9.0.0",
14-
"isomorphic-fetch": "^3.0.0",
1514
"lodash-es": "^4.17.21",
1615
"monaco-editor": "^0.44.0",
1716
"prismjs": "^1.6.0",
@@ -37,7 +36,6 @@
3736
"@babel/preset-react": "^7.0.0",
3837
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
3938
"@types/common-tags": "^1.8.1",
40-
"@types/isomorphic-fetch": "^0.0.36",
4139
"@types/jest": "^29.2.5",
4240
"@types/lodash-es": "^4.17.6",
4341
"@types/prismjs": "^1.26.0",

ui/frontend/pnpm-lock.yaml

Lines changed: 0 additions & 50 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/frontend/reducers/crates.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
22
import { sortBy } from 'lodash-es';
33
import * as z from 'zod';
44

5-
import { adaptFetchError, jsonGet, routes } from '../actions';
5+
import { jsonGet, routes } from '../api';
66
import { Crate } from '../types';
77

88
const sliceName = 'crates';
@@ -17,7 +17,7 @@ const CratesResponse = z.object({
1717
type CratesResponse = z.infer<typeof CratesResponse>;
1818

1919
export const performCratesLoad = createAsyncThunk(sliceName, async () => {
20-
const d = await adaptFetchError(() => jsonGet(routes.meta.crates));
20+
const d = await jsonGet(routes.meta.crates);
2121
const crates = await CratesResponse.parseAsync(d);
2222
return sortBy(crates.crates, (c) => c.name);
2323
});

ui/frontend/reducers/output/clippy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
22
import * as z from 'zod';
33

4-
import { adaptFetchError, jsonPost, routes } from '../../actions';
4+
import { jsonPost, routes } from '../../api';
55
import { clippyRequestSelector } from '../../selectors';
66
import RootState from '../../state';
77

@@ -38,7 +38,7 @@ export const performClippy = createAsyncThunk<ClippyResponseBody, void, { state:
3838
async (_arg: void, { getState }) => {
3939
const body: ClippyRequestBody = clippyRequestSelector(getState());
4040

41-
const d = await adaptFetchError(() => jsonPost(routes.clippy, body));
41+
const d = await jsonPost(routes.clippy, body);
4242
return ClippyResponseBody.parseAsync(d);
4343
},
4444
);

ui/frontend/reducers/output/execute.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { AnyAction, Draft, createAsyncThunk, createSlice } from '@reduxjs/toolkit';
22
import * as z from 'zod';
33

4-
import { SimpleThunkAction, adaptFetchError, jsonPost, routes } from '../../actions';
4+
import { SimpleThunkAction } from '../../actions';
5+
import { jsonPost, routes } from '../../api';
56
import { executeRequestPayloadSelector, executeViaWebsocketSelector } from '../../selectors';
67
import { Channel, Edition, Mode } from '../../types';
78
import {
@@ -76,7 +77,7 @@ const ExecuteResponseBody = z.object({
7677
type ExecuteResponseBody = z.infer<typeof ExecuteResponseBody>;
7778

7879
export const performExecute = createAsyncThunk(sliceName, async (payload: ExecuteRequestBody) => {
79-
const d = await adaptFetchError(() => jsonPost(routes.execute, payload));
80+
const d = await jsonPost(routes.execute, payload);
8081
return ExecuteResponseBody.parseAsync(d);
8182
});
8283

0 commit comments

Comments
 (0)