Skip to content

Commit cb8eac1

Browse files
authored
feat(tags): improve JSON format for Smart Tags (#568)
- deprecates `columns` shortcut - adds `attribute` shortcut (renamed from `columns` for consistency) - adds `constraint` shortcut for table constraints - improves error messages - fixes a bug in warning message that cased error to throw
1 parent 440568b commit cb8eac1

1 file changed

Lines changed: 111 additions & 35 deletions

File tree

packages/graphile-utils/src/makePgSmartTagsPlugin.ts

Lines changed: 111 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SchemaBuilder, Build, Plugin, Options } from "graphile-build";
1+
import { Build, Plugin } from "graphile-build";
22
import { PgEntityKind, PgEntity } from "graphile-build-pg";
33
import { inspect } from "util";
44
import { entityIsIdentifiedBy } from "./introspectionHelpers";
@@ -101,7 +101,7 @@ export function makePgSmartTagsPlugin(
101101
subscribeToUpdatesCallback?: SubscribeToPgSmartTagUpdatesCallback | null
102102
): Plugin {
103103
let [rules, rawRules] = rulesFrom(ruleOrRules);
104-
return (builder: SchemaBuilder, _options: Options) => {
104+
const plugin: Plugin = (builder, _options) => {
105105
if (subscribeToUpdatesCallback) {
106106
builder.registerWatcher(
107107
async triggerRebuild => {
@@ -150,6 +150,7 @@ export function makePgSmartTagsPlugin(
150150
return build;
151151
});
152152
};
153+
return plugin;
153154
}
154155

155156
/**
@@ -163,8 +164,11 @@ export function makePgSmartTagsPlugin(
163164
* my_table: {
164165
* tags: {omit: "create,update,delete"},
165166
* description: "You can overwrite the description too",
166-
* columns: {
167+
* attribute: {
167168
* my_private_field: {tags: {omit: true}}
169+
* },
170+
* constraint: {
171+
* my_constraint: {tags: {omit: true}}
168172
* }
169173
* },
170174
* "my_schema.myOtherTable": {
@@ -187,8 +191,14 @@ export type JSONPgSmartTags = {
187191
[identifier: string]: {
188192
tags?: PgSmartTagTags;
189193
description?: string;
190-
columns?: {
191-
[columnName: string]: {
194+
attribute?: {
195+
[attributeName: string]: {
196+
tags?: PgSmartTagTags;
197+
description?: string;
198+
};
199+
};
200+
constraint?: {
201+
[constraintName: string]: {
192202
tags?: PgSmartTagTags;
193203
description?: string;
194204
};
@@ -211,6 +221,60 @@ function pgSmartTagRulesFromJSON(
211221
}
212222
const specByIdentifierByKind = json.config;
213223
const rules: PgSmartTagRule[] = [];
224+
225+
function process(
226+
kind: PgEntityKind,
227+
identifier: string,
228+
subKind: PgEntityKind,
229+
obj: unknown,
230+
key: string,
231+
deprecated = false
232+
): void {
233+
if (kind !== PgEntityKind.CLASS) {
234+
throw new Error(
235+
`makeJSONPgSmartTagsPlugin: '${key}' is only valid on a class; you tried to set it on a '${kind}' at 'config.${kind}.${identifier}.${key}'`
236+
);
237+
}
238+
const path = `config.${kind}.${identifier}.${key}`;
239+
if (deprecated) {
240+
console.warn(
241+
`Tags JSON path '${path}' is deprecated, please use 'config.${kind}.${identifier}.attribute' instead`
242+
);
243+
}
244+
if (typeof obj !== "object" || obj == null) {
245+
throw new Error(`Invalid value for '${path}'`);
246+
}
247+
const entities: object = obj;
248+
for (const entityName of Object.keys(entities)) {
249+
if (entityName.includes(".")) {
250+
throw new Error(
251+
`${key} '${entityName}' should not contain a period at '${path}'. This nested entry does not need further description.`
252+
);
253+
}
254+
const entitySpec = entities[entityName];
255+
const {
256+
tags: entityTags,
257+
description: entityDescription,
258+
...entityRest
259+
} = entitySpec;
260+
if (Object.keys(entityRest).length > 0) {
261+
console.warn(
262+
`WARNING: makeJSONPgSmartTagsPlugin '${key}' only supports 'tags' and 'description' currently, you have also set '${Object.keys(
263+
entityRest
264+
).join(
265+
"', '"
266+
)}' at '${path}.${entityName}'. Perhaps you forgot to add a "tags" entry containing these keys?`
267+
);
268+
}
269+
rules.push({
270+
kind: subKind,
271+
match: `${identifier}.${entityName}`,
272+
tags: entityTags,
273+
description: entityDescription,
274+
});
275+
}
276+
}
277+
214278
for (const rawKind of Object.keys(specByIdentifierByKind)) {
215279
if (!Object.prototype.hasOwnProperty.call(meaningByKind, rawKind)) {
216280
throw new Error(
@@ -219,52 +283,64 @@ function pgSmartTagRulesFromJSON(
219283
}
220284
const kind: PgEntityKind = rawKind as any;
221285
const specByIdentifier = specByIdentifierByKind[kind];
286+
222287
for (const identifier of Object.keys(specByIdentifier)) {
223288
const spec = specByIdentifier[identifier];
224-
const { tags, description, columns, ...rest } = spec;
289+
const {
290+
tags,
291+
description,
292+
columns,
293+
attribute,
294+
constraint,
295+
...rest
296+
} = spec;
225297
if (Object.keys(rest).length > 0) {
226298
console.warn(
227-
`WARNING: makeJSONPgSmartTagsPlugin only supports tags, description and columns currently, you have also set '${Object.keys(
299+
`WARNING: makeJSONPgSmartTagsPlugin identifier spec only supports 'tags', 'description', 'attribute' and 'constraint' currently, you have also set '${Object.keys(
228300
rest
229-
).join("', '")}'`
301+
).join("', '")}' at 'config.${kind}.${identifier}'`
230302
);
231303
}
304+
232305
rules.push({
233306
kind,
234307
match: identifier,
235308
tags,
236309
description,
237310
});
311+
238312
if (columns) {
239-
if (kind !== PgEntityKind.CLASS) {
240-
throw new Error(
241-
`makeJSONPgSmartTagsPlugin: 'columns' is only valid on a class; you tried to set it on a '${kind}'`
242-
);
243-
}
244-
for (const columnName of Object.keys(columns)) {
245-
const columnSpec = columns[columnName];
246-
const {
247-
tags: columnTags,
248-
description: columnDescription,
249-
...columnRest
250-
} = columnSpec;
251-
if (Object.keys(columnRest).length > 0) {
252-
console.warn(
253-
`WARNING: makeJSONPgSmartTagsPlugin columns only supports tags and description currently, you have also set '${columnRest.join(
254-
"', '"
255-
)}'`
256-
);
257-
}
258-
rules.push({
259-
kind: PgEntityKind.ATTRIBUTE,
260-
match: `${identifier}.${columnName}`,
261-
tags: columnTags,
262-
description: columnDescription,
263-
});
264-
}
313+
// This was in graphile-utils 4.0.0 but was deprecated in 4.0.1 for consistency reasons.
314+
process(
315+
kind,
316+
identifier,
317+
PgEntityKind.ATTRIBUTE,
318+
columns,
319+
"columns",
320+
true
321+
);
322+
}
323+
if (attribute) {
324+
process(
325+
kind,
326+
identifier,
327+
PgEntityKind.ATTRIBUTE,
328+
attribute,
329+
"attribute"
330+
);
331+
}
332+
if (constraint) {
333+
process(
334+
kind,
335+
identifier,
336+
PgEntityKind.CONSTRAINT,
337+
constraint,
338+
"constraint"
339+
);
265340
}
266341
}
267342
}
343+
268344
return rules;
269345
}
270346

@@ -279,7 +355,7 @@ export type SubscribeToJSONPgSmartTagsUpdatesCallback = (
279355
export function makeJSONPgSmartTagsPlugin(
280356
json: JSONPgSmartTags | null,
281357
subscribeToJSONUpdatesCallback?: SubscribeToJSONPgSmartTagsUpdatesCallback | null
282-
) {
358+
): Plugin {
283359
// Get rules from JSON
284360
let rules = pgSmartTagRulesFromJSON(json);
285361

0 commit comments

Comments
 (0)