Skip to content

Commit 98f359d

Browse files
committed
feat: add support for getting field nested runtime path
1 parent df292ee commit 98f359d

23 files changed

+710
-34
lines changed

src/compiler/fields/array_field.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,10 @@
1010
import type { CompilerField, CompilerParent } from '../../types.js'
1111

1212
export function createArrayField(parent: CompilerParent): CompilerField {
13-
/**
14-
* Commented to see if a use case arrives for using this.
15-
*/
16-
// const fieldPathExpression =
17-
// parent.fieldPathExpression !== `''`
18-
// ? `${parent.fieldPathExpression} + '.' + ${parent.variableName}_i`
19-
// : `${parent.variableName}_i`
20-
2113
const wildCardPath = parent.wildCardPath !== '' ? `${parent.wildCardPath}.*` : `*`
2214

2315
return {
16+
parentExpression: parent.variableName,
2417
parentValueExpression: `${parent.variableName}.value`,
2518
fieldNameExpression: `${parent.variableName}_i`,
2619
fieldPathExpression: wildCardPath,

src/compiler/fields/object_field.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,11 @@ export function createObjectField(
1414
variablesCounter: number,
1515
parent: CompilerParent
1616
): CompilerField {
17-
/**
18-
* Commented to see if a use case arrives for using this.
19-
*/
20-
// const fieldPathExpression =
21-
// parent.fieldPathExpression !== `''`
22-
// ? `${parent.fieldPathExpression} + '.' + '${node.fieldName}'`
23-
// : `'${node.fieldName}'`
24-
2517
const wildCardPath =
2618
parent.wildCardPath !== '' ? `${parent.wildCardPath}.${node.fieldName}` : node.fieldName
2719

2820
return {
21+
parentExpression: parent.variableName,
2922
parentValueExpression: `${parent.variableName}.value`,
3023
fieldNameExpression: `'${node.fieldName}'`,
3124
fieldPathExpression: wildCardPath,

src/compiler/fields/record_field.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,10 @@
1010
import type { CompilerField, CompilerParent } from '../../types.js'
1111

1212
export function createRecordField(parent: CompilerParent): CompilerField {
13-
/**
14-
* Commented to see if a use case arrives for using this.
15-
*/
16-
// const fieldPathExpression =
17-
// parent.fieldPathExpression !== `''`
18-
// ? `${parent.fieldPathExpression} + '.' + ${parent.variableName}_i`
19-
// : `${parent.variableName}_i`
20-
2113
const wildCardPath = parent.wildCardPath !== '' ? `${parent.wildCardPath}.*` : `*`
2214

2315
return {
16+
parentExpression: parent.variableName,
2417
parentValueExpression: `${parent.variableName}.value`,
2518
fieldNameExpression: `${parent.variableName}_i`,
2619
fieldPathExpression: wildCardPath,

src/compiler/fields/root_field.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { CompilerField, CompilerParent } from '../../types.js'
1111

1212
export function createRootField(parent: CompilerParent): CompilerField {
1313
return {
14+
parentExpression: parent.variableName,
1415
parentValueExpression: parent.variableName,
1516
fieldNameExpression: `''`,
1617
fieldPathExpression: `''`,

src/compiler/fields/tuple_field.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,11 @@ export function createTupleField(
1313
node: Pick<FieldNode, 'fieldName' | 'propertyName'>,
1414
parent: CompilerParent
1515
): CompilerField {
16-
/**
17-
* Commented to see if a use case arrives for using this.
18-
*/
19-
// const fieldPathExpression =
20-
// parent.fieldPathExpression !== `''`
21-
// ? `${parent.fieldPathExpression} + '.' + '${node.fieldName}'`
22-
// : `'${node.fieldName}'`
23-
2416
const wildCardPath =
2517
parent.wildCardPath !== '' ? `${parent.wildCardPath}.${node.fieldName}` : node.fieldName
2618

2719
return {
20+
parentExpression: parent.variableName,
2821
parentValueExpression: `${parent.variableName}.value`,
2922
fieldNameExpression: `${node.fieldName}`,
3023
fieldPathExpression: wildCardPath,

src/compiler/nodes/base.ts

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export abstract class BaseNode {
4040
defineFieldVariables({
4141
fieldNameExpression: this.field.fieldNameExpression,
4242
isArrayMember: this.field.isArrayMember,
43+
parentExpression: this.field.parentExpression,
4344
parentValueExpression: this.field.parentValueExpression,
4445
valueExpression: this.field.valueExpression,
4546
variableName: this.field.variableName,

src/scripts/define_inline_functions.ts

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ export function defineInlineFunctions(options: { convertEmptyStringsToNull: bool
1616
field.isValid = false;
1717
errorReporter.report(messagesProvider.getMessage(message, rule, field, args), rule, field, args);
1818
};
19+
function memo(fn) {
20+
let value;
21+
return () => {
22+
if (value) {
23+
return value
24+
}
25+
value = fn()
26+
return value
27+
}
28+
};
1929
function defineValue(value, field) {
2030
${options.convertEmptyStringsToNull ? `if (value === '') { value = null; }` : ''}
2131
field.value = value;

src/scripts/field/variables.ts

+26
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import { RefIdentifier } from '../../types.js'
1111

1212
type FieldOptions = {
13+
parentExpression: string
1314
variableName: string
1415
valueExpression: string
1516
fieldNameExpression: string
@@ -30,6 +31,7 @@ export function defineFieldVariables({
3031
wildCardPath,
3132
isArrayMember,
3233
valueExpression,
34+
parentExpression,
3335
fieldNameExpression,
3436
parentValueExpression,
3537
}: FieldOptions) {
@@ -41,11 +43,35 @@ export function defineFieldVariables({
4143
})`
4244
: valueExpression
4345

46+
/**
47+
* Field path output expression is the value that is
48+
* returned when "field.getFieldPath" method is
49+
* called.
50+
*/
51+
let fieldPathOutputExpression = ''
52+
53+
/**
54+
* When we are a direct member of a root field, we will not prefix the root path, since
55+
* there is no path to prefix, its just root.
56+
*/
57+
if (parentExpression === 'root' || parentExpression === 'root_item') {
58+
fieldPathOutputExpression = fieldNameExpression
59+
} else if (fieldNameExpression !== "''") {
60+
/**
61+
* When we are the root ourselves, we will return an empty string
62+
* value.
63+
*/
64+
fieldPathOutputExpression = `${parentExpression}.getFieldPath() + '.' + ${fieldNameExpression}`
65+
}
66+
4467
return `const ${variableName} = defineValue(${inValueExpression}, {
4568
data: root,
4669
meta: meta,
4770
name: ${fieldNameExpression},
4871
wildCardPath: '${wildCardPath}',
72+
getFieldPath: memo(() => {
73+
return ${fieldPathOutputExpression};
74+
}),
4975
mutate: defineValue,
5076
report: report,
5177
isValid: true,

src/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ export type FieldContext = {
9494
*/
9595
isDefined: boolean
9696

97+
/**
98+
* Returns the nested path to the field. The parts
99+
* are joined by a dot notation.
100+
*/
101+
getFieldPath(): string
102+
97103
/**
98104
* Wildcard path for the field. The value is a nested
99105
* pointer to the field under validation.
@@ -446,6 +452,7 @@ export type CompilerParent = {
446452
* names for the JS output.
447453
*/
448454
export type CompilerField = {
455+
parentExpression: string
449456
parentValueExpression: string
450457
fieldNameExpression: string
451458
fieldPathExpression: string

tests/integration/compiler/array_node.spec.ts

+55
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,61 @@ test.group('Array node', () => {
789789
data[0] = 'foo'
790790
assert.deepEqual(output, [])
791791
})
792+
793+
test('get nested runtime field path', async ({ assert }) => {
794+
const compiler = new Compiler({
795+
type: 'root',
796+
schema: {
797+
type: 'array',
798+
bail: true,
799+
fieldName: '*',
800+
validations: [],
801+
propertyName: '*',
802+
allowNull: false,
803+
isOptional: false,
804+
each: {
805+
type: 'array',
806+
bail: true,
807+
fieldName: '*',
808+
validations: [],
809+
propertyName: '*',
810+
allowNull: false,
811+
isOptional: false,
812+
each: {
813+
type: 'literal',
814+
bail: true,
815+
fieldName: '*',
816+
allowNull: false,
817+
isOptional: false,
818+
propertyName: '*',
819+
validations: [],
820+
transformFnId: 'ref://1',
821+
},
822+
},
823+
},
824+
})
825+
826+
const data: any = [['hello world'], ['hi world']]
827+
const meta = {}
828+
829+
const refs = refsBuilder()
830+
refs.trackTransformer((value, ctx) => {
831+
assert.oneOf(ctx.getFieldPath(), ['0.0', '1.0'])
832+
assert.equal(ctx.wildCardPath, '*.*')
833+
return value
834+
})
835+
836+
const messagesProvider = new MessagesProviderFactory().create()
837+
const errorReporter = new ErrorReporterFactory().create()
838+
839+
const fn = compiler.compile()
840+
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
841+
assert.deepEqual(output, [['hello world'], ['hi world']])
842+
843+
// Mutation test
844+
data[0][0] = 'foo'
845+
assert.deepEqual(output, [['hello world'], ['hi world']])
846+
})
792847
})
793848

794849
test.group('Array node | optional: true', () => {

tests/integration/compiler/literal_node.spec.ts

+34
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,40 @@ test.group('Literal node', () => {
527527
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
528528
assert.deepEqual(output, 'virk')
529529
})
530+
531+
test('get nested runtime field path', async ({ assert }) => {
532+
assert.plan(3)
533+
534+
const compiler = new Compiler({
535+
type: 'root',
536+
schema: {
537+
type: 'literal',
538+
bail: true,
539+
fieldName: '*',
540+
validations: [],
541+
propertyName: '*',
542+
allowNull: false,
543+
isOptional: false,
544+
transformFnId: 'ref://1',
545+
},
546+
})
547+
548+
const data = 'virk'
549+
const meta = {}
550+
const refs = refsBuilder()
551+
refs.trackTransformer((value, ctx) => {
552+
assert.equal(value, 'virk')
553+
assert.equal(ctx.getFieldPath(), '')
554+
return value
555+
})
556+
557+
const messagesProvider = new MessagesProviderFactory().create()
558+
const errorReporter = new ErrorReporterFactory().create()
559+
560+
const fn = compiler.compile()
561+
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
562+
assert.deepEqual(output, 'virk')
563+
})
530564
})
531565

532566
test.group('Literal node | optional: true', () => {

tests/integration/compiler/object_node.spec.ts

+91
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,97 @@ test.group('Object node', () => {
10381038
data.profile = {}
10391039
assert.deepEqual(output, {})
10401040
})
1041+
1042+
test('get nested runtime field path', async ({ assert }) => {
1043+
const compiler = new Compiler({
1044+
type: 'root',
1045+
schema: {
1046+
type: 'object',
1047+
groups: [],
1048+
bail: true,
1049+
fieldName: '*',
1050+
validations: [],
1051+
propertyName: '*',
1052+
allowNull: false,
1053+
isOptional: false,
1054+
allowUnknownProperties: false,
1055+
properties: [
1056+
{
1057+
type: 'object',
1058+
groups: [],
1059+
bail: true,
1060+
fieldName: 'social',
1061+
validations: [],
1062+
propertyName: 'social',
1063+
allowNull: false,
1064+
isOptional: false,
1065+
allowUnknownProperties: false,
1066+
properties: [
1067+
{
1068+
type: 'literal',
1069+
bail: true,
1070+
fieldName: 'twitter_handle',
1071+
allowNull: false,
1072+
isOptional: false,
1073+
propertyName: 'twitterHandle',
1074+
validations: [],
1075+
transformFnId: 'ref://1',
1076+
},
1077+
{
1078+
type: 'literal',
1079+
bail: true,
1080+
fieldName: 'github_username',
1081+
allowNull: false,
1082+
isOptional: false,
1083+
propertyName: 'githubUsername',
1084+
validations: [],
1085+
transformFnId: 'ref://2',
1086+
},
1087+
],
1088+
},
1089+
],
1090+
},
1091+
})
1092+
1093+
const data = {
1094+
social: {
1095+
github_username: 'thetutlage',
1096+
twitter_handle: 'AmanVirk1',
1097+
},
1098+
}
1099+
1100+
const refs = refsBuilder()
1101+
refs.trackTransformer((value, ctx) => {
1102+
assert.equal(ctx.getFieldPath(), 'social.twitter_handle')
1103+
return value
1104+
})
1105+
refs.trackTransformer((value, ctx) => {
1106+
assert.equal(ctx.getFieldPath(), 'social.github_username')
1107+
return value
1108+
})
1109+
1110+
const meta = {}
1111+
const messagesProvider = new MessagesProviderFactory().create()
1112+
const errorReporter = new ErrorReporterFactory().create()
1113+
1114+
const fn = compiler.compile()
1115+
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
1116+
assert.deepEqual(output, {
1117+
social: {
1118+
githubUsername: 'thetutlage',
1119+
twitterHandle: 'AmanVirk1',
1120+
},
1121+
})
1122+
1123+
// Mutation test:
1124+
data.social.github_username = 'foo'
1125+
assert.deepEqual(output, {
1126+
social: {
1127+
githubUsername: 'thetutlage',
1128+
twitterHandle: 'AmanVirk1',
1129+
},
1130+
})
1131+
})
10411132
})
10421133

10431134
test.group('Object node | optional: true', () => {

0 commit comments

Comments
 (0)