Skip to content

Commit

Permalink
feat: add support for getting field nested runtime path
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Mar 28, 2024
1 parent df292ee commit 98f359d
Show file tree
Hide file tree
Showing 23 changed files with 710 additions and 34 deletions.
9 changes: 1 addition & 8 deletions src/compiler/fields/array_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@
import type { CompilerField, CompilerParent } from '../../types.js'

export function createArrayField(parent: CompilerParent): CompilerField {
/**
* Commented to see if a use case arrives for using this.
*/
// const fieldPathExpression =
// parent.fieldPathExpression !== `''`
// ? `${parent.fieldPathExpression} + '.' + ${parent.variableName}_i`
// : `${parent.variableName}_i`

const wildCardPath = parent.wildCardPath !== '' ? `${parent.wildCardPath}.*` : `*`

return {
parentExpression: parent.variableName,
parentValueExpression: `${parent.variableName}.value`,
fieldNameExpression: `${parent.variableName}_i`,
fieldPathExpression: wildCardPath,
Expand Down
9 changes: 1 addition & 8 deletions src/compiler/fields/object_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,11 @@ export function createObjectField(
variablesCounter: number,
parent: CompilerParent
): CompilerField {
/**
* Commented to see if a use case arrives for using this.
*/
// const fieldPathExpression =
// parent.fieldPathExpression !== `''`
// ? `${parent.fieldPathExpression} + '.' + '${node.fieldName}'`
// : `'${node.fieldName}'`

const wildCardPath =
parent.wildCardPath !== '' ? `${parent.wildCardPath}.${node.fieldName}` : node.fieldName

return {
parentExpression: parent.variableName,
parentValueExpression: `${parent.variableName}.value`,
fieldNameExpression: `'${node.fieldName}'`,
fieldPathExpression: wildCardPath,
Expand Down
9 changes: 1 addition & 8 deletions src/compiler/fields/record_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@
import type { CompilerField, CompilerParent } from '../../types.js'

export function createRecordField(parent: CompilerParent): CompilerField {
/**
* Commented to see if a use case arrives for using this.
*/
// const fieldPathExpression =
// parent.fieldPathExpression !== `''`
// ? `${parent.fieldPathExpression} + '.' + ${parent.variableName}_i`
// : `${parent.variableName}_i`

const wildCardPath = parent.wildCardPath !== '' ? `${parent.wildCardPath}.*` : `*`

return {
parentExpression: parent.variableName,
parentValueExpression: `${parent.variableName}.value`,
fieldNameExpression: `${parent.variableName}_i`,
fieldPathExpression: wildCardPath,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/fields/root_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { CompilerField, CompilerParent } from '../../types.js'

export function createRootField(parent: CompilerParent): CompilerField {
return {
parentExpression: parent.variableName,
parentValueExpression: parent.variableName,
fieldNameExpression: `''`,
fieldPathExpression: `''`,
Expand Down
9 changes: 1 addition & 8 deletions src/compiler/fields/tuple_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,11 @@ export function createTupleField(
node: Pick<FieldNode, 'fieldName' | 'propertyName'>,
parent: CompilerParent
): CompilerField {
/**
* Commented to see if a use case arrives for using this.
*/
// const fieldPathExpression =
// parent.fieldPathExpression !== `''`
// ? `${parent.fieldPathExpression} + '.' + '${node.fieldName}'`
// : `'${node.fieldName}'`

const wildCardPath =
parent.wildCardPath !== '' ? `${parent.wildCardPath}.${node.fieldName}` : node.fieldName

return {
parentExpression: parent.variableName,
parentValueExpression: `${parent.variableName}.value`,
fieldNameExpression: `${node.fieldName}`,
fieldPathExpression: wildCardPath,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/nodes/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export abstract class BaseNode {
defineFieldVariables({
fieldNameExpression: this.field.fieldNameExpression,
isArrayMember: this.field.isArrayMember,
parentExpression: this.field.parentExpression,
parentValueExpression: this.field.parentValueExpression,
valueExpression: this.field.valueExpression,
variableName: this.field.variableName,
Expand Down
10 changes: 10 additions & 0 deletions src/scripts/define_inline_functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ export function defineInlineFunctions(options: { convertEmptyStringsToNull: bool
field.isValid = false;
errorReporter.report(messagesProvider.getMessage(message, rule, field, args), rule, field, args);
};
function memo(fn) {
let value;
return () => {
if (value) {
return value
}
value = fn()
return value
}
};
function defineValue(value, field) {
${options.convertEmptyStringsToNull ? `if (value === '') { value = null; }` : ''}
field.value = value;
Expand Down
26 changes: 26 additions & 0 deletions src/scripts/field/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { RefIdentifier } from '../../types.js'

type FieldOptions = {
parentExpression: string
variableName: string
valueExpression: string
fieldNameExpression: string
Expand All @@ -30,6 +31,7 @@ export function defineFieldVariables({
wildCardPath,
isArrayMember,
valueExpression,
parentExpression,
fieldNameExpression,
parentValueExpression,
}: FieldOptions) {
Expand All @@ -41,11 +43,35 @@ export function defineFieldVariables({
})`
: valueExpression

/**
* Field path output expression is the value that is
* returned when "field.getFieldPath" method is
* called.
*/
let fieldPathOutputExpression = ''

/**
* When we are a direct member of a root field, we will not prefix the root path, since
* there is no path to prefix, its just root.
*/
if (parentExpression === 'root' || parentExpression === 'root_item') {
fieldPathOutputExpression = fieldNameExpression
} else if (fieldNameExpression !== "''") {
/**
* When we are the root ourselves, we will return an empty string
* value.
*/
fieldPathOutputExpression = `${parentExpression}.getFieldPath() + '.' + ${fieldNameExpression}`
}

return `const ${variableName} = defineValue(${inValueExpression}, {
data: root,
meta: meta,
name: ${fieldNameExpression},
wildCardPath: '${wildCardPath}',
getFieldPath: memo(() => {
return ${fieldPathOutputExpression};
}),
mutate: defineValue,
report: report,
isValid: true,
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ export type FieldContext = {
*/
isDefined: boolean

/**
* Returns the nested path to the field. The parts
* are joined by a dot notation.
*/
getFieldPath(): string

/**
* Wildcard path for the field. The value is a nested
* pointer to the field under validation.
Expand Down Expand Up @@ -446,6 +452,7 @@ export type CompilerParent = {
* names for the JS output.
*/
export type CompilerField = {
parentExpression: string
parentValueExpression: string
fieldNameExpression: string
fieldPathExpression: string
Expand Down
55 changes: 55 additions & 0 deletions tests/integration/compiler/array_node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,61 @@ test.group('Array node', () => {
data[0] = 'foo'
assert.deepEqual(output, [])
})

test('get nested runtime field path', async ({ assert }) => {
const compiler = new Compiler({
type: 'root',
schema: {
type: 'array',
bail: true,
fieldName: '*',
validations: [],
propertyName: '*',
allowNull: false,
isOptional: false,
each: {
type: 'array',
bail: true,
fieldName: '*',
validations: [],
propertyName: '*',
allowNull: false,
isOptional: false,
each: {
type: 'literal',
bail: true,
fieldName: '*',
allowNull: false,
isOptional: false,
propertyName: '*',
validations: [],
transformFnId: 'ref://1',
},
},
},
})

const data: any = [['hello world'], ['hi world']]
const meta = {}

const refs = refsBuilder()
refs.trackTransformer((value, ctx) => {
assert.oneOf(ctx.getFieldPath(), ['0.0', '1.0'])
assert.equal(ctx.wildCardPath, '*.*')
return value
})

const messagesProvider = new MessagesProviderFactory().create()
const errorReporter = new ErrorReporterFactory().create()

const fn = compiler.compile()
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
assert.deepEqual(output, [['hello world'], ['hi world']])

// Mutation test
data[0][0] = 'foo'
assert.deepEqual(output, [['hello world'], ['hi world']])
})
})

test.group('Array node | optional: true', () => {
Expand Down
34 changes: 34 additions & 0 deletions tests/integration/compiler/literal_node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,40 @@ test.group('Literal node', () => {
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
assert.deepEqual(output, 'virk')
})

test('get nested runtime field path', async ({ assert }) => {
assert.plan(3)

const compiler = new Compiler({
type: 'root',
schema: {
type: 'literal',
bail: true,
fieldName: '*',
validations: [],
propertyName: '*',
allowNull: false,
isOptional: false,
transformFnId: 'ref://1',
},
})

const data = 'virk'
const meta = {}
const refs = refsBuilder()
refs.trackTransformer((value, ctx) => {
assert.equal(value, 'virk')
assert.equal(ctx.getFieldPath(), '')
return value
})

const messagesProvider = new MessagesProviderFactory().create()
const errorReporter = new ErrorReporterFactory().create()

const fn = compiler.compile()
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
assert.deepEqual(output, 'virk')
})
})

test.group('Literal node | optional: true', () => {
Expand Down
91 changes: 91 additions & 0 deletions tests/integration/compiler/object_node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,97 @@ test.group('Object node', () => {
data.profile = {}
assert.deepEqual(output, {})
})

test('get nested runtime field path', async ({ assert }) => {
const compiler = new Compiler({
type: 'root',
schema: {
type: 'object',
groups: [],
bail: true,
fieldName: '*',
validations: [],
propertyName: '*',
allowNull: false,
isOptional: false,
allowUnknownProperties: false,
properties: [
{
type: 'object',
groups: [],
bail: true,
fieldName: 'social',
validations: [],
propertyName: 'social',
allowNull: false,
isOptional: false,
allowUnknownProperties: false,
properties: [
{
type: 'literal',
bail: true,
fieldName: 'twitter_handle',
allowNull: false,
isOptional: false,
propertyName: 'twitterHandle',
validations: [],
transformFnId: 'ref://1',
},
{
type: 'literal',
bail: true,
fieldName: 'github_username',
allowNull: false,
isOptional: false,
propertyName: 'githubUsername',
validations: [],
transformFnId: 'ref://2',
},
],
},
],
},
})

const data = {
social: {
github_username: 'thetutlage',
twitter_handle: 'AmanVirk1',
},
}

const refs = refsBuilder()
refs.trackTransformer((value, ctx) => {
assert.equal(ctx.getFieldPath(), 'social.twitter_handle')
return value
})
refs.trackTransformer((value, ctx) => {
assert.equal(ctx.getFieldPath(), 'social.github_username')
return value
})

const meta = {}
const messagesProvider = new MessagesProviderFactory().create()
const errorReporter = new ErrorReporterFactory().create()

const fn = compiler.compile()
const output = await fn(data, meta, refs.toJSON(), messagesProvider, errorReporter)
assert.deepEqual(output, {
social: {
githubUsername: 'thetutlage',
twitterHandle: 'AmanVirk1',
},
})

// Mutation test:
data.social.github_username = 'foo'
assert.deepEqual(output, {
social: {
githubUsername: 'thetutlage',
twitterHandle: 'AmanVirk1',
},
})
})
})

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

0 comments on commit 98f359d

Please sign in to comment.