@@ -12,6 +12,7 @@ export function createValidationChecker(schema) {
1212 const scopes = new Map ( ) ;
1313
1414 function createScopes ( jsonSchema , key = 'root' ) {
15+ const sampleEmptyObject = buildSampleEmptyObject ( schema ) ;
1516 scopes . set ( key , createValidationsScope ( jsonSchema ) ) ;
1617 Object . entries ( jsonSchema ?. properties ?? { } )
1718 . filter ( ( [ , property ] ) => property . type === 'object' || property . type === 'array' )
@@ -21,6 +22,8 @@ export function createValidationChecker(schema) {
2122 }
2223 createScopes ( property , key ) ;
2324 } ) ;
25+
26+ validateInlineRules ( jsonSchema , sampleEmptyObject ) ;
2427 }
2528
2629 createScopes ( schema ) ;
@@ -44,12 +47,25 @@ function createValidationsScope(schema) {
4447
4548 const validations = Object . entries ( logic . validations ?? { } ) ;
4649 const computedValues = Object . entries ( logic . computedValues ?? { } ) ;
50+ const sampleEmptyObject = buildSampleEmptyObject ( schema ) ;
4751
4852 validations . forEach ( ( [ id , validation ] ) => {
53+ if ( ! validation . rule ) {
54+ throw Error ( `Missing rule for validation with id of: "${ id } ".` ) ;
55+ }
56+
57+ checkRuleIntegrity ( validation . rule , id , sampleEmptyObject ) ;
58+
4959 validationMap . set ( id , validation ) ;
5060 } ) ;
5161
5262 computedValues . forEach ( ( [ id , computedValue ] ) => {
63+ if ( ! computedValue . rule ) {
64+ throw Error ( `Missing rule for computedValue with id of: "${ id } ".` ) ;
65+ }
66+
67+ checkRuleIntegrity ( computedValue . rule , id , sampleEmptyObject ) ;
68+
5369 computedValuesMap . set ( id , computedValue ) ;
5470 } ) ;
5571
@@ -65,8 +81,11 @@ function createValidationsScope(schema) {
6581 const validation = validationMap . get ( id ) ;
6682 return evaluateValidation ( validation . rule , values ) ;
6783 } ,
68- evaluateComputedValueRuleForField ( id , values ) {
84+ evaluateComputedValueRuleForField ( id , values , fieldName ) {
6985 const validation = computedValuesMap . get ( id ) ;
86+ if ( validation === undefined )
87+ throw Error ( `"${ id } " computedValue in field "${ fieldName } " doesn't exist.` ) ;
88+
7089 return evaluateValidation ( validation . rule , values ) ;
7190 } ,
7291 evaluateComputedValueRuleInCondition ( id , values ) {
@@ -125,6 +144,9 @@ function replaceHandlebarsTemplates({
125144 } else if ( typeof toReplace === 'object' ) {
126145 const { value, ...rules } = toReplace ;
127146
147+ if ( Object . keys ( rules ) . length > 1 && ! value )
148+ throw Error ( 'Cannot define multiple rules without a template string with key `value`.' ) ;
149+
128150 const computedTemplateValue = Object . entries ( rules ) . reduce ( ( prev , [ key , rule ] ) => {
129151 const computedValue = validations . getScope ( parentID ) . evaluateValidation ( rule , formValues ) ;
130152 return prev . replaceAll ( `{{${ key } }}` , computedValue ) ;
@@ -212,3 +234,71 @@ function handleNestedObjectForComputedValues(values, formValues, parentID, valid
212234 } )
213235 ) ;
214236}
237+
238+ function buildSampleEmptyObject ( schema = { } ) {
239+ const sample = { } ;
240+ if ( typeof schema !== 'object' || ! schema . properties ) {
241+ return schema ;
242+ }
243+
244+ for ( const key in schema . properties ) {
245+ if ( schema . properties [ key ] . type === 'object' ) {
246+ sample [ key ] = buildSampleEmptyObject ( schema . properties [ key ] ) ;
247+ } else if ( schema . properties [ key ] . type === 'array' ) {
248+ const itemSchema = schema . properties [ key ] . items ;
249+ sample [ key ] = buildSampleEmptyObject ( itemSchema ) ;
250+ } else {
251+ sample [ key ] = true ;
252+ }
253+ }
254+
255+ return sample ;
256+ }
257+
258+ function validateInlineRules ( jsonSchema , sampleEmptyObject ) {
259+ const properties = ( jsonSchema ?. properties || jsonSchema ?. items ?. properties ) ?? { } ;
260+ Object . entries ( properties )
261+ . filter ( ( [ , property ] ) => property [ 'x-jsf-logic-computedAttrs' ] !== undefined )
262+ . forEach ( ( [ fieldName , property ] ) => {
263+ Object . entries ( property [ 'x-jsf-logic-computedAttrs' ] )
264+ . filter ( ( [ , value ] ) => typeof value === 'object' )
265+ . forEach ( ( [ key , item ] ) => {
266+ Object . values ( item ) . forEach ( ( rule ) => {
267+ checkRuleIntegrity (
268+ rule ,
269+ fieldName ,
270+ sampleEmptyObject ,
271+ ( item ) =>
272+ `"${ item . var } " in inline rule in property "${ fieldName } .x-jsf-logic-computedAttrs.${ key } " does not exist as a JSON schema property.`
273+ ) ;
274+ } ) ;
275+ } ) ;
276+ } ) ;
277+ }
278+
279+ function checkRuleIntegrity (
280+ rule ,
281+ id ,
282+ data ,
283+ errorMessage = ( item ) => `"${ item . var } " in rule "${ id } " does not exist as a JSON schema property.`
284+ ) {
285+ Object . values ( rule ?? { } ) . map ( ( subRule ) => {
286+ if ( ! Array . isArray ( subRule ) && subRule !== null && subRule !== undefined ) return ;
287+ subRule . map ( ( item ) => {
288+ const isVar = item !== null && typeof item === 'object' && Object . hasOwn ( item , 'var' ) ;
289+ if ( isVar ) {
290+ const exists = jsonLogic . apply ( { var : removeIndicesFromPath ( item . var ) } , data ) ;
291+ if ( exists === null ) {
292+ throw Error ( errorMessage ( item ) ) ;
293+ }
294+ } else {
295+ checkRuleIntegrity ( item , id , data ) ;
296+ }
297+ } ) ;
298+ } ) ;
299+ }
300+
301+ function removeIndicesFromPath ( path ) {
302+ const intermediatePath = path . replace ( / \. \d + \. / g, '.' ) ;
303+ return intermediatePath . replace ( / \. \d + $ / , '' ) ;
304+ }
0 commit comments