Skip to content

Commit 7502c83

Browse files
authored
feat!: init readonly field validators (#185)
* Add test definition * Generate json schema for local use * Use local schema everywhere * Rename Validator to TypeValidator * Update definition and rules * Update type validator * Add missing validator * Fixes * Init read-only field validator declarations * Fix * Generate rules predicate in generator * Make sure renderer has no other responsibilities * Fix tests * Rename `validatorNamePattern` to `typeValidatorNamePattern` * Add changeset * Rename `validatorParamName` to `typeValidatorParamName` * Add changeset * Change generation structure * Rename methods * Implement ReadonlyFieldValidatorPredicate * Generate readonly field validator * Fixes * Fix * Implement `MapDiffHasAffectedKeysPredicate` * Commit desired output * Fix * Vertical alignment for 'or' predicate * Implement config options * Update tests * Better organized predicates * Implement `typeHasReadonlyField` func * Readonly field predicate creators * Init tests for readonly field predicates * Fix `readonlyFieldPredicateForObjectType` * Add ReferencePredicate * Init discriminated union predicates * Add user pet to test definition * Fix incorrect filtering * Update firestore.rules * Change expected generation * Disable tests * Add todo * Add another todo
1 parent 8faa1be commit 7502c83

31 files changed

Lines changed: 1955 additions & 360 deletions

.changeset/selfish-cows-smash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"typesync-cli": minor
3+
---
4+
5+
[BREAKING] Renamed the `validatorNamePattern` option for the `generate-rules` command to `typeValidatorNamePattern`.

.changeset/wicked-goats-type.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"typesync-cli": minor
3+
---
4+
5+
[BREAKING] Renamed the `validatorParamName` option for the `generate-rules` command to `typeValidatorParamName`.

.circleci/config.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ jobs:
9999
- run:
100100
name: Run rests for the source code
101101
command: yarn test:src
102-
- run:
103-
name: Run Security Rules tests
104-
command: yarn test:rules
102+
# TODO: Implement
103+
# - run:
104+
# name: Run Security Rules tests
105+
# command: yarn test:rules
105106

106107
publish-npm-package:
107108
executor: node/default

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@
4444
"format-check:src": "prettier \"src/**/*.(js|ts|json)\" --check",
4545
"format:package-json": "sort-package-json \"package.json\"",
4646
"format:src": "prettier --write \"src/**/*.(js|ts|json)\"",
47-
"generate-json-schema": "tsx scripts/generate-json-schema",
47+
"generate-json-schema": "tsx scripts/generate-json-schema current-version",
48+
"generate-json-schema-local": "tsx scripts/generate-json-schema local",
4849
"lint": "eslint src --ext .ts",
4950
"test": "yarn run test:compile && yarn run test:src --verbose && yarn run test:rules",
5051
"test:compile": "tsc -p tsconfig.test.json",
51-
"test:rules": "firebase emulators:exec --project demo-rules --only firestore 'jest --testMatch \"<rootDir>/tests/security/**/*.test.ts\"'",
52+
"test:rules": "tsx src/cli/index.tsx generate-rules --definition tests/security/definition.yml --outFile tests/security/firestore.rules && firebase emulators:exec --project demo-rules --only firestore 'jest --testMatch \"<rootDir>/tests/security/**/*.test.ts\"'",
5253
"test:src": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
5354
"version": "changeset version && yarn run generate-json-schema"
5455
},

schema.local.json

Lines changed: 459 additions & 0 deletions
Large diffs are not rendered by default.

scripts/generate-json-schema.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { format } from 'prettier';
33
import { zodToJsonSchema } from 'zod-to-json-schema';
44

55
import { definition } from '../src/definition/index.js';
6-
import { assert } from '../src/util/assert.js';
6+
import { assert, assertNever } from '../src/util/assert.js';
77
import { extractPackageJsonVersion } from '../src/util/extract-package-json-version.js';
88
import { getDirName, writeFile } from '../src/util/fs.js';
99

@@ -14,8 +14,8 @@ function inferCurrentSchemaVersion() {
1414
return `v${major}.${minor}`;
1515
}
1616

17-
function generateJsonSchema(minorVersionName: string) {
18-
return zodToJsonSchema(definition.zodSchema, minorVersionName);
17+
function generateJsonSchema(name: string) {
18+
return zodToJsonSchema(definition.zodSchema, name);
1919
}
2020

2121
type JsonSchema = ReturnType<typeof generateJsonSchema>;
@@ -30,9 +30,27 @@ async function writeJsonSchemaToFile(jsonSchema: JsonSchema, outPath: string) {
3030
}
3131

3232
async function main() {
33-
const minorVersionName = inferCurrentSchemaVersion();
34-
const jsonSchema = generateJsonSchema(minorVersionName);
35-
const pathToOutputFile = resolve(getDirName(import.meta.url), `../public/${minorVersionName}.json`);
33+
const [, , type] = process.argv;
34+
assert(
35+
type === 'current-version' || type === 'local',
36+
`The 'type' argument for generate-json-schema must be one of 'current-version' or 'local'.`
37+
);
38+
39+
let schemaName: string | undefined;
40+
let pathToOutputFile: string | undefined;
41+
42+
if (type === 'current-version') {
43+
const minorVersionName = inferCurrentSchemaVersion();
44+
schemaName = minorVersionName;
45+
pathToOutputFile = resolve(getDirName(import.meta.url), `../public/${minorVersionName}.json`);
46+
} else if (type === 'local') {
47+
schemaName = 'local';
48+
pathToOutputFile = resolve(getDirName(import.meta.url), `../schema.local.json`);
49+
} else {
50+
assertNever(type);
51+
}
52+
53+
const jsonSchema = generateJsonSchema(schemaName);
3654
await writeJsonSchemaToFile(jsonSchema, pathToOutputFile);
3755
}
3856

src/api/rules.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ import { GenerateRepresentationResult } from './_common.js';
33

44
export interface GenerateRulesRepresentationOptions {
55
definition: string;
6+
typeValidatorNamePattern?: string;
7+
typeValidatorParamName?: string;
8+
readonlyFieldValidatorNamePattern?: string;
9+
readonlyFieldValidatorPrevDataParamName?: string;
10+
readonlyFieldValidatorNextDataParamName?: string;
611
debug?: boolean;
712
}
813

914
export interface GenerateRulesOptions extends GenerateRulesRepresentationOptions {
1015
outFile: string;
1116
startMarker?: string;
1217
endMarker?: string;
13-
validatorNamePattern?: string;
14-
validatorParamName?: string;
1518
indentation?: number;
1619
}
1720

src/cli/index.tsx

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@ import {
1919
DEFAULT_RULES_DEBUG,
2020
DEFAULT_RULES_END_MARKER,
2121
DEFAULT_RULES_INDENTATION,
22+
DEFAULT_RULES_READONLY_FIELD_VALIDATOR_NAME_PATTERN,
23+
DEFAULT_RULES_READONLY_FIELD_VALIDATOR_NEXT_DATA_PARAM_NAME,
24+
DEFAULT_RULES_READONLY_FIELD_VALIDATOR_PREV_DATA_PARAM_NAME,
2225
DEFAULT_RULES_START_MARKER,
23-
DEFAULT_RULES_VALIDATOR_NAME_PATTERN,
24-
DEFAULT_RULES_VALIDATOR_PARAM_NAME,
26+
DEFAULT_RULES_TYPE_VALIDATOR_NAME_PATTERN,
27+
DEFAULT_RULES_TYPE_VALIDATOR_PARAM_NAME,
2528
DEFAULT_SWIFT_DEBUG,
2629
DEFAULT_SWIFT_INDENTATION,
2730
DEFAULT_TS_DEBUG,
2831
DEFAULT_TS_INDENTATION,
2932
DEFAULT_TS_OBJECT_TYPE_FORMAT,
3033
DEFAULT_VALIDATE_DEBUG,
31-
RULES_VALIDATOR_NAME_PATTERN_PARAM,
34+
RULES_READONLY_FIELD_VALIDATOR_NAME_PATTERN_PARAM,
35+
RULES_TYPE_VALIDATOR_NAME_PATTERN_PARAM,
3236
} from '../constants.js';
3337
import { extractErrorMessage } from '../util/extract-error-message.js';
3438
import { extractPackageJsonVersion } from '../util/extract-package-json-version.js';
@@ -263,17 +267,38 @@ await yargs(hideBin(process.argv))
263267
demandOption: false,
264268
default: DEFAULT_RULES_END_MARKER,
265269
})
266-
.option('validatorNamePattern', {
267-
describe: `The pattern that specifies how the validators are named. The string must contain the '${RULES_VALIDATOR_NAME_PATTERN_PARAM}' substring (this is a literal value). For example, providing 'isValid${RULES_VALIDATOR_NAME_PATTERN_PARAM}' ensures that the generated validators are given names like 'isValidUser', 'isValidProject' etc.`,
270+
.option('typeValidatorNamePattern', {
271+
describe: `The pattern that specifies how the generated type validators are named. The pattern must be a string that contains the '${RULES_TYPE_VALIDATOR_NAME_PATTERN_PARAM}' substring (this is a literal value). For example, providing 'isValid${RULES_TYPE_VALIDATOR_NAME_PATTERN_PARAM}' ensures that the generated validators are given names like 'isValidUser', 'isValidProject' etc.`,
268272
type: 'string',
269273
demandOption: false,
270-
default: DEFAULT_RULES_VALIDATOR_NAME_PATTERN,
274+
default: DEFAULT_RULES_TYPE_VALIDATOR_NAME_PATTERN,
271275
})
272-
.option('validatorParamName', {
276+
.option('typeValidatorParamName', {
273277
describe: 'The name of the parameter taken by each type validator.',
274278
type: 'string',
275279
demandOption: false,
276-
default: DEFAULT_RULES_VALIDATOR_PARAM_NAME,
280+
default: DEFAULT_RULES_TYPE_VALIDATOR_PARAM_NAME,
281+
})
282+
// TODO: Implement
283+
// .option('readonlyFieldValidatorNamePattern', {
284+
// describe: `The pattern that specifies how the generated readonly field validators are named. The pattern must be a string that contains the '${RULES_READONLY_FIELD_VALIDATOR_NAME_PATTERN_PARAM}' substring (this is a literal value). For example, providing 'isReadonlyFieldAffectedFor${RULES_READONLY_FIELD_VALIDATOR_NAME_PATTERN_PARAM}' ensures that the generated validators are given names like 'isReadonlyFieldAffectedForUser', 'isReadonlyFieldAffectedForProject' etc.`,
285+
// type: 'string',
286+
// demandOption: false,
287+
// default: DEFAULT_RULES_READONLY_FIELD_VALIDATOR_NAME_PATTERN,
288+
// })
289+
// .option('readonlyFieldValidatorPrevDataParamName', {
290+
// describe:
291+
// 'The name of the first parameter taken by each readonly field validator representing previous data. This parameter used when computing the diff between next data and previous data to determine whether a readonly field has been affected by a write.',
292+
// type: 'string',
293+
// demandOption: false,
294+
// default: DEFAULT_RULES_READONLY_FIELD_VALIDATOR_PREV_DATA_PARAM_NAME,
295+
// })
296+
.option('readonlyFieldValidatorNextDataParamName', {
297+
describe:
298+
'The name of the second parameter taken by each readonly field validator representing next data. This parameter used when computing the diff between next data and previous data to determine whether a readonly field has been affected by a write.',
299+
type: 'string',
300+
demandOption: false,
301+
default: DEFAULT_RULES_READONLY_FIELD_VALIDATOR_NEXT_DATA_PARAM_NAME,
277302
})
278303
.option('indentation', {
279304
describe: 'Indentation or tab width for the generated code.',
@@ -293,8 +318,11 @@ await yargs(hideBin(process.argv))
293318
outFile,
294319
startMarker,
295320
endMarker,
296-
validatorNamePattern,
297-
validatorParamName,
321+
typeValidatorNamePattern,
322+
typeValidatorParamName,
323+
readonlyFieldValidatorNamePattern,
324+
readonlyFieldValidatorPrevDataParamName,
325+
readonlyFieldValidatorNextDataParamName,
298326
indentation,
299327
debug,
300328
} = args;
@@ -306,8 +334,11 @@ await yargs(hideBin(process.argv))
306334
outFile: pathToOutputFile,
307335
startMarker,
308336
endMarker,
309-
validatorNamePattern,
310-
validatorParamName,
337+
typeValidatorNamePattern,
338+
typeValidatorParamName,
339+
// readonlyFieldValidatorNamePattern,
340+
// readonlyFieldValidatorPrevDataParamName,
341+
// readonlyFieldValidatorNextDataParamName,
311342
indentation,
312343
debug,
313344
});

src/constants.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type { SchemaGraphOrientation } from './api/graph.js';
22
import type { TSObjectTypeFormat } from './api/ts.js';
33

44
export const PYTHON_UNDEFINED_SENTINEL_CLASS = 'TypesyncUndefined';
5-
export const RULES_VALIDATOR_NAME_PATTERN_PARAM = '{modelName}';
5+
export const RULES_TYPE_VALIDATOR_NAME_PATTERN_PARAM = '{modelName}';
6+
export const RULES_READONLY_FIELD_VALIDATOR_NAME_PATTERN_PARAM = '{modelName}';
67

78
/*
89
* Default values
@@ -21,8 +22,11 @@ export const DEFAULT_PY_DEBUG = false;
2122

2223
export const DEFAULT_RULES_START_MARKER = 'typesync-start';
2324
export const DEFAULT_RULES_END_MARKER = 'typesync-end';
24-
export const DEFAULT_RULES_VALIDATOR_NAME_PATTERN = `isValid${RULES_VALIDATOR_NAME_PATTERN_PARAM}`;
25-
export const DEFAULT_RULES_VALIDATOR_PARAM_NAME = 'data';
25+
export const DEFAULT_RULES_TYPE_VALIDATOR_NAME_PATTERN = `isValid${RULES_TYPE_VALIDATOR_NAME_PATTERN_PARAM}`;
26+
export const DEFAULT_RULES_TYPE_VALIDATOR_PARAM_NAME = 'data';
27+
export const DEFAULT_RULES_READONLY_FIELD_VALIDATOR_NAME_PATTERN = `isReadonlyFieldAffectedFor${RULES_TYPE_VALIDATOR_NAME_PATTERN_PARAM}`;
28+
export const DEFAULT_RULES_READONLY_FIELD_VALIDATOR_PREV_DATA_PARAM_NAME = 'prevData';
29+
export const DEFAULT_RULES_READONLY_FIELD_VALIDATOR_NEXT_DATA_PARAM_NAME = 'nextData';
2630
export const DEFAULT_RULES_INDENTATION = 2;
2731
export const DEFAULT_RULES_DEBUG = false;
2832

src/core/__tests__/definitions/bad-field.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"bad": "../../../../public/v0.9.json",
2+
"bad": "../../../../schema.local.json",
33
"Username": {
44
"model": "alias",
55
"type": "string"

0 commit comments

Comments
 (0)