1- import { AuthorizationModel , CheckRequestTupleKey , Condition , ListObjectsRequest , RelationReference , TupleKey , TypeDefinition } from "@openfga/sdk" ;
1+ import {
2+ AuthorizationModel ,
3+ CheckRequestTupleKey ,
4+ Condition ,
5+ ListObjectsRequest ,
6+ RelationReference ,
7+ TupleKey ,
8+ TypeDefinition ,
9+ } from "@openfga/sdk" ;
210import Ajv , { Schema , ValidateFunction , SchemaValidateFunction } from "ajv" ;
311
4-
512type Store = {
6- name : string
7- model_file ?: string
8- model ?: string
9- tuple_files ?: string
10- tuples : TupleKey [ ]
11- tests : Test [ ]
12- }
13+ name : string ;
14+ model_file ?: string ;
15+ model ?: string ;
16+ tuple_files ?: string ;
17+ tuples : TupleKey [ ] ;
18+ tests : Test [ ] ;
19+ } ;
1320
1421type Test = {
15- tuples : TupleKey [ ]
16- check : CheckTest [ ]
17- list_objects : ListObjectTest [ ]
18- }
22+ tuples : TupleKey [ ] ;
23+ check : CheckTest [ ] ;
24+ list_objects : ListObjectTest [ ] ;
25+ } ;
1926
2027type CheckTest = Omit < CheckRequestTupleKey , "relation" > & {
21- assertions : Record < string , boolean >
22- context : Record < string , any >
23- }
28+ assertions : Record < string , boolean > ;
29+ context : Record < string , any > ;
30+ } ;
2431
2532type ListObjectTest = Omit < ListObjectsRequest , "relation" > & {
26- assertions : Record < string , boolean >
27- }
33+ assertions : Record < string , boolean > ;
34+ } ;
2835
29- type BaseError = { keyword : string ; message : string ; instancePath : string ; }
36+ type BaseError = { keyword : string ; message : string ; instancePath : string } ;
3037
3138// Errors for tuples validation
3239const invalidTuple = ( message : string , instancePath : string ) : BaseError => {
3340 return {
3441 keyword : "valid_tuple" ,
3542 message,
36- instancePath
43+ instancePath,
3744 } ;
3845} ;
3946
@@ -46,15 +53,21 @@ const relationMustExistOnType = (relation: string, type: string, instancePath: s
4653} ;
4754
4855const userNotTypeRestriction = ( user : string , tuple : TupleKey , instancePath : string ) => {
49- return invalidTuple ( `'${ user } ' is not a type restriction on relation '${ tuple . relation } ' of type '${ tuple . object . split ( ":" ) [ 0 ] } '.` , instancePath + "/user" ) ;
56+ return invalidTuple (
57+ `'${ user } ' is not a type restriction on relation '${ tuple . relation } ' of type '${ tuple . object . split ( ":" ) [ 0 ] } '.` ,
58+ instancePath + "/user" ,
59+ ) ;
5060} ;
5161
5262const conditionDoesntExist = ( tuple : TupleKey , instancePath : string ) => {
5363 return invalidTuple ( `condition '${ tuple . condition ?. name } ' is not defined.` , instancePath + "/condition/name" ) ;
5464} ;
5565
5666const notAParameter = ( param : string , tuple : TupleKey , instancePath : string ) => {
57- return invalidTuple ( `'${ param } ' is not a parameter on condition '${ tuple . condition ?. name } '.` , instancePath + `/condition/context/${ param } ` ) ;
67+ return invalidTuple (
68+ `'${ param } ' is not a parameter on condition '${ tuple . condition ?. name } '.` ,
69+ instancePath + `/condition/context/${ param } ` ,
70+ ) ;
5871} ;
5972
6073// Errors for store validation
@@ -80,8 +93,10 @@ const invalidTypeUser = (type: string, types: string[], instancePath: string) =>
8093
8194const nonMatchingRelationType = ( relation : string , user : string , values : string [ ] , instancePath : string ) => {
8295 if ( values . length ) {
83- return invalidStore ( `\`${ relation } \` is not a relation on \`${ user } \`, and does not exist in model - valid relations are [${ values } ].` , instancePath ) ;
84-
96+ return invalidStore (
97+ `\`${ relation } \` is not a relation on \`${ user } \`, and does not exist in model - valid relations are [${ values } ].` ,
98+ instancePath ,
99+ ) ;
85100 }
86101 return invalidStore ( `\`${ relation } \` is not a relation on \`${ user } \`, and does not exist in model.` , instancePath ) ;
87102} ;
@@ -91,14 +106,17 @@ const invalidAssertion = (assertion: string, object: string, instancePath: strin
91106} ;
92107
93108const unidentifiedTestParam = ( testParam : string , instancePath : string ) => {
94- return invalidStore ( `\`${ testParam } \` is not a recognized paramaeter for any condition defined in the model.` , instancePath ) ;
109+ return invalidStore (
110+ `\`${ testParam } \` is not a recognized paramaeter for any condition defined in the model.` ,
111+ instancePath ,
112+ ) ;
95113} ;
96114
97- const undefinedUserTuple = ( user : string , instancePath : string ) => {
115+ const undefinedTypeTuple = ( user : string , instancePath : string ) => {
98116 return {
99117 keyword : "valid_store_warning" ,
100118 message : `${ user } does not match any existing tuples; the check is still valid - but double check to ensure this is intended.` ,
101- instancePath : instancePath + "/user"
119+ instancePath,
102120 } ;
103121} ;
104122
@@ -335,27 +353,42 @@ function validateUserField(model: AuthorizationModel, types: string[], userField
335353 if ( userField . includes ( "#" ) ) {
336354 const [ type , relation ] = userField . split ( "#" ) ;
337355
338- const userRelations = model . type_definitions . filter ( typeDef => typeDef . type === user ) . flatMap ( ( typeDef ) => {
339- const relationArray : string [ ] = [ ] ;
340- for ( const rel in typeDef . relations ) {
341- relationArray . push ( type + "#" + rel ) ;
342- }
343- return relationArray ;
344- } ) ;
356+ const userRelations = model . type_definitions
357+ . filter ( ( typeDef ) => typeDef . type === user )
358+ . flatMap ( ( typeDef ) => {
359+ const relationArray : string [ ] = [ ] ;
360+ for ( const rel in typeDef . relations ) {
361+ relationArray . push ( type + "#" + rel ) ;
362+ }
363+ return relationArray ;
364+ } ) ;
345365
346366 if ( ! userRelations . includes ( userField ) ) {
347- errors . push ( nonMatchingRelationType ( relation , user , userRelations . map ( rel => rel . split ( "#" ) [ 1 ] ) , instancePath + "/user" ) ) ;
367+ errors . push (
368+ nonMatchingRelationType (
369+ relation ,
370+ user ,
371+ userRelations . map ( ( rel ) => rel . split ( "#" ) [ 1 ] ) ,
372+ instancePath + "/user" ,
373+ ) ,
374+ ) ;
348375 }
349-
350376 }
351377 return errors ;
352378}
353379
354- function validateAssertionField ( model : AuthorizationModel , typeField : string , assertions : Record < string , any > , instancePath : string ) {
380+ function validateAssertionField (
381+ model : AuthorizationModel ,
382+ typeField : string ,
383+ assertions : Record < string , any > ,
384+ instancePath : string ,
385+ ) {
355386 const errors = [ ] ;
356387
357388 // Validate assertions exist as relations
358- const typesRelations = model . type_definitions . filter ( tuple => tuple . type === typeField ) . map ( tuple => tuple . relations ) ;
389+ const typesRelations = model . type_definitions
390+ . filter ( ( tuple ) => tuple . type === typeField )
391+ . map ( ( tuple ) => tuple . relations ) ;
359392 for ( const assertion in assertions ) {
360393 for ( const relation in typesRelations ) {
361394 if ( ! typesRelations [ relation ] ?. [ assertion ] ) {
@@ -368,55 +401,75 @@ function validateAssertionField(model: AuthorizationModel, typeField: string, as
368401}
369402
370403// Validate Check Tuple
371- function validateCheck ( model : AuthorizationModel , checkTest : CheckTest , tuples : TupleKey [ ] , params : string [ ] , instancePath : string ) {
372- const errors = [ ] ;
404+ function validateCheck (
405+ model : AuthorizationModel ,
406+ checkTest : CheckTest ,
407+ tuples : TupleKey [ ] ,
408+ params : string [ ] ,
409+ instancePath : string ,
410+ ) {
411+ const userErrors = [ ] ;
373412
374- const types = model . type_definitions . map ( d => d . type ) ;
413+ const types = model . type_definitions . map ( ( d ) => d . type ) ;
375414
376415 const checkUser = checkTest . user ;
377416 const checkObject = checkTest . object ;
378417
379- errors . push ( ...validateUserField ( model , types , checkUser , instancePath ) ) ;
418+ userErrors . push ( ...validateUserField ( model , types , checkUser , instancePath ) ) ;
380419
381- if ( ! errors . length ) {
382- if ( ! tuples . map ( tuple => tuple . user ) . filter ( user => user === checkUser ) . length ) {
383- errors . push ( undefinedUserTuple ( checkUser , instancePath ) ) ;
420+ if ( ! userErrors . length ) {
421+ if ( ! tuples . map ( ( tuple ) => tuple . user ) . filter ( ( user ) => user === checkUser ) . length ) {
422+ userErrors . push ( undefinedTypeTuple ( checkUser , instancePath + "/user" ) ) ;
384423 }
385424 }
386425
426+ const objectErrors = [ ] ;
427+
387428 const object = checkObject . split ( ":" ) [ 0 ] ;
388429
389430 // Ensure valid type of object
390431 if ( ! types . includes ( object ) ) {
391- errors . push ( invalidTypeUser ( object , types , instancePath + "/object" ) ) ;
432+ objectErrors . push ( invalidTypeUser ( object , types , instancePath + "/object" ) ) ;
392433 }
393434
394- errors . push ( ...validateAssertionField ( model , object , checkTest . assertions , instancePath ) ) ;
435+ if ( ! objectErrors . length ) {
436+ if ( ! tuples . map ( ( tuple ) => tuple . object ) . filter ( ( object ) => object === checkObject ) . length ) {
437+ objectErrors . push ( undefinedTypeTuple ( checkObject , instancePath + "/object" ) ) ;
438+ }
439+ }
440+
441+ objectErrors . push ( ...validateAssertionField ( model , object , checkTest . assertions , instancePath ) ) ;
395442
396443 const context = checkTest . context ;
397444 for ( const testParam in context ) {
398445 if ( ! params . includes ( testParam ) ) {
399- errors . push ( unidentifiedTestParam ( testParam , instancePath + `/context/${ testParam } ` ) ) ;
446+ objectErrors . push ( unidentifiedTestParam ( testParam , instancePath + `/context/${ testParam } ` ) ) ;
400447 }
401448 }
402449
403- return errors ;
450+ return [ ... userErrors , ... objectErrors ] ;
404451}
405452
406453// Validate List Object
407- function validateListObject ( model : AuthorizationModel , listObjects : ListObjectTest , tuples : TupleKey [ ] , params : string [ ] , instancePath : string ) {
454+ function validateListObject (
455+ model : AuthorizationModel ,
456+ listObjects : ListObjectTest ,
457+ tuples : TupleKey [ ] ,
458+ params : string [ ] ,
459+ instancePath : string ,
460+ ) {
408461 const errors = [ ] ;
409462
410- const types = model . type_definitions . map ( d => d . type ) ;
463+ const types = model . type_definitions . map ( ( d ) => d . type ) ;
411464
412465 const listUser = listObjects . user ;
413466 const listType = listObjects . type ;
414467
415468 errors . push ( ...validateUserField ( model , types , listUser , instancePath ) ) ;
416469
417- if ( ! errors . length ) {
418- if ( ! tuples . map ( tuple => tuple . user ) . filter ( user => user === listUser ) . length ) {
419- errors . push ( undefinedUserTuple ( listUser , instancePath ) ) ;
470+ if ( ! errors . length ) {
471+ if ( ! tuples . map ( ( tuple ) => tuple . user ) . filter ( ( user ) => user === listUser ) . length ) {
472+ errors . push ( undefinedTypeTuple ( listUser , instancePath + "/user" ) ) ;
420473 }
421474 }
422475
@@ -467,15 +520,31 @@ function validateTestTypes(store: Store, model: AuthorizationModel, instancePath
467520 if ( ! test . check [ checkNo ] . user || ! test . check [ checkNo ] . object ) {
468521 return false ;
469522 }
470- errors . push ( ...validateCheck ( model , test . check [ checkNo ] , tuples , params , instancePath + `/tests/${ testNo } /check/${ checkNo } ` ) ) ;
523+ errors . push (
524+ ...validateCheck (
525+ model ,
526+ test . check [ checkNo ] ,
527+ tuples ,
528+ params ,
529+ instancePath + `/tests/${ testNo } /check/${ checkNo } ` ,
530+ ) ,
531+ ) ;
471532 }
472533
473534 // Validate list objects
474535 for ( const listNo in test . list_objects ) {
475536 if ( ! test . list_objects [ listNo ] . user || ! test . list_objects [ listNo ] . type ) {
476537 return false ;
477538 }
478- errors . push ( ...validateListObject ( model , test . list_objects [ listNo ] , tuples , params , instancePath + `/tests/${ testNo } /list_objects/${ listNo } ` ) ) ;
539+ errors . push (
540+ ...validateListObject (
541+ model ,
542+ test . list_objects [ listNo ] ,
543+ tuples ,
544+ params ,
545+ instancePath + `/tests/${ testNo } /list_objects/${ listNo } ` ,
546+ ) ,
547+ ) ;
479548 }
480549 }
481550
@@ -486,7 +555,11 @@ function validateTestTypes(store: Store, model: AuthorizationModel, instancePath
486555 return true ;
487556}
488557
489- const validateStore : SchemaValidateFunction = function ( this : { jsonModel : AuthorizationModel } , store : Store , cxt : { instancePath : string } ) : boolean {
558+ const validateStore : SchemaValidateFunction = function (
559+ this : { jsonModel : AuthorizationModel } ,
560+ store : Store ,
561+ cxt : { instancePath : string } ,
562+ ) : boolean {
490563 validateStore . errors = validateStore . errors || [ ] ;
491564
492565 // Require model or model_file
0 commit comments