Skip to content

Commit f84d27e

Browse files
Infer schema type from generator
1 parent 3291f34 commit f84d27e

3 files changed

Lines changed: 68 additions & 24 deletions

File tree

‎app/model/schemas.ts‎

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
1-
export const identifiers = {
1+
export const types = {
22
string: "string",
33
number: "number",
4+
array: "array",
45
} as const;
56

6-
export const read = {
7-
string: (name: string) => ({
8-
type: identifiers.string,
7+
function readString(name: string) {
8+
return {
9+
type: types.string,
10+
name,
11+
};
12+
}
13+
14+
function readNumber(name: string) {
15+
return {
16+
type: types.number,
917
name,
10-
}),
11-
number: (name: string) => ({
12-
type: identifiers.number,
18+
};
19+
}
20+
21+
function readArray(name: string, itemType: typeof types.string | typeof types.number) {
22+
return {
23+
type: types.array,
1324
name,
14-
}),
25+
itemType,
26+
};
27+
}
28+
29+
export const read = {
30+
string: readString,
31+
number: readNumber,
32+
array: readArray,
1533
};
1634

17-
export type SchemaMessage = ReturnType<typeof read.string | typeof read.number>;
35+
export type SchemaMessagePrimitive = ReturnType<
36+
typeof readString | typeof readNumber
37+
>;
38+
export type SchemaMessage = ReturnType<
39+
typeof readString | typeof readNumber | typeof readArray
40+
>;
1841
export type SchemaReply = string | number;
1942
// export type SchemaGenerator<Result> = Generator<ReturnType<typeof read.string>, Result, string> | Generator<ReturnType<typeof read.number>, Result, number>;
2043
export type SchemaGenerator<Result> = Generator<
@@ -24,7 +47,7 @@ export type SchemaGenerator<Result> = Generator<
2447
>;
2548

2649
export function parseJSON<Result>(
27-
source: Record<string, any>,
50+
source: unknown,
2851
generator: () => SchemaGenerator<Result>
2952
) {
3053
const gen = generator();
@@ -38,13 +61,21 @@ export function parseJSON<Result>(
3861

3962
reply = undefined;
4063

41-
if (result.value.type === identifiers.string) {
42-
if (typeof source[result.value.name] === "string") {
43-
reply = source[result.value.name];
44-
}
45-
} else if (result.value.type === identifiers.number) {
46-
if (typeof source[result.value.name] === "number") {
47-
reply = source[result.value.name];
64+
if (source instanceof Object && result.value.name in source) {
65+
if (result.value.type === types.string) {
66+
if (typeof (source as any)[result.value.name] === "string") {
67+
reply = (source as any)[result.value.name];
68+
}
69+
} else if (result.value.type === types.number) {
70+
if (typeof (source as any)[result.value.name] === "number") {
71+
reply = (source as any)[result.value.name];
72+
}
73+
} else if (result.value.type === types.array) {
74+
const itemType = result.value.itemType;
75+
if (Array.isArray((source as any)[result.value.name])) {
76+
const sanitizedItems = (source as any)[result.value.name].map((x: any) => typeof x === itemType ? x : null);
77+
reply = sanitizedItems;
78+
}
4879
}
4980
}
5081
}
@@ -65,11 +96,11 @@ export function parseFormData<Result>(
6596

6697
reply = undefined;
6798

68-
if (result.value.type === identifiers.string) {
99+
if (result.value.type === types.string) {
69100
if (source.has(result.value.name)) {
70101
reply = source.get(result.value.name) as string;
71102
}
72-
} else if (result.value.type === identifiers.number) {
103+
} else if (result.value.type === types.number) {
73104
if (source.has(result.value.name)) {
74105
reply = parseFloat(source.get(result.value.name) as string);
75106
}

‎app/routes/make/schema.tsx‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import {
1414
parseJSON,
1515
read,
1616
SchemaGenerator,
17+
types,
1718
} from "../../model/schemas";
1819
import { formatJavaScript } from "../../view/codeFormatting";
19-
import { Await } from "../../types/helpers";
20+
import { Await, FinalResult } from "../../types/helpers";
2021
import { fetchController, FetchGenerator, getJSON } from "../../model/fetching";
22+
import { useMemo } from "react";
2123

2224
export let meta: MetaFunction = () => {
2325
return {
@@ -109,35 +111,41 @@ interface SwapiPersonData {
109111
name: string;
110112
height: string;
111113
mass: string;
114+
films: string[];
112115
}
113116
function* SwapiPersonSchema(): SchemaGenerator<SwapiPersonData> {
114117
const name: string = yield read.string("name");
115118
const height: string = yield read.string("height");
116119
const mass: string = yield read.string("mass");
120+
const films: string[] = yield read.array("films", types.string);
117121

118122
return {
119123
name,
120124
height,
121125
mass,
126+
films
122127
};
123128
}
124-
function* SwapiPersonResource(): FetchGenerator<SwapiPersonData> {
125-
const rawData = yield getJSON(new URL(`https://swapi.py4e.com/api/people/1`));
129+
function* SwapiPersonResource() {
130+
const rawData: unknown = yield getJSON(new URL(`https://swapi.py4e.com/api/people/1`));
126131

127132
const parsedData = parseJSON(rawData, function* Schema() {
128133
const name: string = yield read.string("name");
129134
const height: string = yield read.string("height");
130135
const mass: string = yield read.string("mass");
136+
const films: string[] = yield read.array("films", types.string);
131137

132138
return {
133139
name,
134140
height,
135-
mass: mass,
141+
mass,
142+
films
136143
};
137144
});
138145

139146
return parsedData;
140147
}
148+
type SwapiPersonData2 = FinalResult<ReturnType<typeof SwapiPersonResource>>
141149

142150
export default function MakeRenderer() {
143151
const data: Await<ReturnType<typeof loader>> = useRouteData();
@@ -167,7 +175,10 @@ export default function MakeRenderer() {
167175
const personData =
168176
personDataRaw != null ? parseJSON(personDataRaw, SwapiPersonSchema) : null;
169177

170-
const [personPromise] = useState(() => fetchController(new URL('https://swapi.py4e.com/'), SwapiPersonResource));
178+
const personPromise = useMemo(
179+
() => fetchController(new URL("https://swapi.py4e.com/"), SwapiPersonResource),
180+
[]
181+
);
171182
const personData2 = usePromise(personPromise);
172183

173184
return (

‎app/types/helpers.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export type Await<Awaitable> = Awaitable extends Promise<infer Value> ? Value : Awaitable;
2+
3+
export type FinalResult<Type> = Type extends Generator<any, infer Result> ? Result : Type;

0 commit comments

Comments
 (0)