CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/fill/define.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Assign, RequiredKeys } from "utility-types";
7
import { Optionals } from "./types";
8
9
type Requireds<T> = Pick<T, RequiredKeys<T>>;
10
11
export const required = "__!!!!!!this is a required property!!!!!!__";
12
13
type Definition<T> = {
14
[K in keyof T]: {} extends Pick<T, K> ? T[K] : typeof required;
15
};
16
17
/**
18
* `define<T, U>(props: unknown, definition)`
19
* Guarantees at runtime that `props` matches `definition`
20
* `U` must only be the optional params on `T`
21
*
22
* @return {object} T where provided defaults are guaranteed
23
*
24
* @example
25
*
26
* define<{name: string,
27
* highlight?: boolean,
28
* last?: string},
29
* {highlight: boolean}>(unknown_prop, {highlight: false});
30
*
31
* Unfortunately you must use both type annotations until this goes through
32
* https://github.com/microsoft/TypeScript/issues/26242
33
*
34
**/
35
export function define<T>(props: unknown, definition: Definition<T>): T;
36
export function define<T extends object, U extends Optionals<T>>(
37
props: unknown,
38
definition: Assign<Definition<T>, U>,
39
allow_extra?: boolean,
40
strict?: boolean
41
): Assign<U, Requireds<T>>;
42
export function define<T extends object, U extends Optionals<T>>(
43
props: unknown,
44
definition: Assign<Definition<T>, U>,
45
allow_extra = false,
46
strict = false
47
): Assign<U, Requireds<T>> {
48
// We put explicit traces before the errors in this function,
49
// since otherwise they can be very hard to debug.
50
function maybe_error(message: string): any {
51
const err = `${message} ${error_addendum(props, definition)}`;
52
if (strict) {
53
throw new Error(err);
54
} else {
55
console.log(err);
56
console.trace();
57
return definition as any;
58
}
59
}
60
61
if (props == undefined) {
62
props = {};
63
}
64
// Undefined was checked above but TS 3.6.3 is having none of it.
65
// Checking here makes TS work as expected below
66
if (typeof props !== "object" || props == undefined) {
67
return maybe_error(
68
`BUG -- Traceback -- misc.defaults -- TypeError: function takes inputs as an object`
69
);
70
}
71
const result: Assign<U, Requireds<T>> = {} as any;
72
for (const key in definition) {
73
if (props.hasOwnProperty(key) && props[key] != undefined) {
74
if (definition[key] === required && props[key] == undefined) {
75
return maybe_error(
76
`misc.defaults -- TypeError: property '${key}' must be specified on props:`
77
);
78
}
79
result[key] = props[key];
80
} else if (definition[key] != undefined) {
81
if (definition[key] == required) {
82
maybe_error(
83
`misc.defaults -- TypeError: property '${key}' must be specified:`
84
);
85
} else {
86
result[key] = definition[key];
87
}
88
}
89
}
90
91
if (!allow_extra) {
92
for (const key in props) {
93
if (!definition.hasOwnProperty(key)) {
94
return maybe_error(
95
`misc.defaults -- TypeError: got an unexpected argument '${key}'`
96
);
97
}
98
}
99
}
100
return result;
101
}
102
103
function error_addendum(props: unknown, definition: unknown) {
104
try {
105
return `(obj1=${exports.trunc(
106
exports.to_json(props),
107
1024
108
)}, obj2=${exports.trunc(exports.to_json(definition), 1024)})`;
109
} catch (err) {
110
return "";
111
}
112
}
113
114