@@ -151,6 +151,14 @@ export default function getSchema(provider) {
151151 RECOGNIZED_METADATA . push ( 'authorization_details_types' ) ;
152152 }
153153
154+ let CHOICES = { } ;
155+
156+ if ( features . rpMetadataChoices . enabled ) {
157+ CHOICES = Object . fromEntries ( Object . entries ( CLIENT_ATTRIBUTES . CHOICES )
158+ . filter ( ( [ key ] ) => RECOGNIZED_METADATA . includes ( key ) ) ) ;
159+ RECOGNIZED_METADATA . push ( ...Object . values ( CHOICES ) ) ;
160+ }
161+
154162 instance ( provider ) . RECOGNIZED_METADATA = RECOGNIZED_METADATA ;
155163
156164 const ENUM = {
@@ -208,18 +216,32 @@ export default function getSchema(provider) {
208216 ctx ,
209217 processCustomMetadata = ! ! configuration . extraClientMetadata . properties . length ,
210218 ) {
219+ this . #initialize( metadata ) ;
220+
221+ if ( processCustomMetadata ) {
222+ this . processCustomMetadata ( ctx ) ;
223+ this . #initialize( this ) ;
224+ }
225+
226+ this . ensureStripUnrecognized ( ) ;
227+ this . ensureStripChoices ( ) ;
228+ }
229+
230+ #initialize( metadata ) {
211231 Object . assign (
212232 this ,
213233 omitBy (
214234 pick ( DEFAULTS , ...RECOGNIZED_METADATA ) ,
215- isUndefined ,
235+ ( value , key ) => isUndefined ( value )
236+ || ( key in CHOICES && metadata [ CHOICES [ key ] ] !== undefined ) ,
216237 ) ,
217238 omitBy (
218239 pick ( metadata , ...RECOGNIZED_METADATA , ...configuration . extraClientMetadata . properties ) ,
219240 isUndefined ,
220241 ) ,
221242 ) ;
222243
244+ this . choices ( ) ;
223245 this . required ( ) ;
224246 this . booleans ( ) ;
225247 this . whens ( ) ;
@@ -311,11 +333,11 @@ export default function getSchema(provider) {
311333 this . invalidate ( 'only one tls_client_auth certificate subject value must be provided' ) ;
312334 }
313335 } else {
314- delete this . tls_client_auth_san_dns ;
315- delete this . tls_client_auth_san_email ;
316- delete this . tls_client_auth_san_ip ;
317- delete this . tls_client_auth_san_uri ;
318- delete this . tls_client_auth_subject_dn ;
336+ this . #unset ( ' tls_client_auth_san_dns' ) ;
337+ this . #unset ( ' tls_client_auth_san_email' ) ;
338+ this . #unset ( ' tls_client_auth_san_ip' ) ;
339+ this . #unset ( ' tls_client_auth_san_uri' ) ;
340+ this . #unset ( ' tls_client_auth_subject_dn' ) ;
319341 }
320342 }
321343
@@ -325,16 +347,50 @@ export default function getSchema(provider) {
325347 if ( this . jwks !== undefined && this . jwks_uri !== undefined ) {
326348 this . invalidate ( 'jwks and jwks_uri must not be used at the same time' ) ;
327349 }
350+ }
328351
329- if ( processCustomMetadata ) {
330- this . processCustomMetadata ( ctx ) ;
331- }
352+ choices ( ) {
353+ for ( const [ target , choice ] of Object . entries ( CHOICES ) ) {
354+ if ( this [ choice ] !== undefined ) {
355+ if ( ! Array . isArray ( this [ choice ] ) ) {
356+ this . invalidate ( `${ choice } must be an array` ) ;
357+ }
358+ const choices = new Set ( this [ choice ] ) ;
332359
333- this . ensureStripUnrecognized ( ) ;
360+ if ( this [ target ] !== undefined && ! choices . has ( this [ target ] ) ) {
361+ this . invalidate ( `${ choice } must include the value of provided ${ target } ` ) ;
362+ }
334363
335- if ( processCustomMetadata ) {
336- // eslint-disable-next-line no-constructor-return
337- return new Schema ( this , ctx , false ) ;
364+ const only = ENUM [ target ] ( this ) ;
365+
366+ // test the options in the following order:
367+ // - explicit value (if provided)
368+ // - ...rest
369+ const options = new Set ( ) ;
370+ if ( this [ target ] ) {
371+ options . add ( this [ target ] ) ;
372+ }
373+ for ( const value of choices ) {
374+ if ( typeof value !== 'string' || ! value . length ) {
375+ this . invalidate ( `${ choice } must only contain strings` ) ;
376+ }
377+ options . add ( value ) ;
378+ }
379+
380+ for ( const option of options ) {
381+ try {
382+ this [ target ] = option ;
383+ this . #enum( target , only ) ;
384+ break ;
385+ } catch {
386+ this . #unset( target ) ;
387+ }
388+ }
389+
390+ if ( ! this [ target ] ) {
391+ this . invalidate ( `${ choice } includes no supported values` ) ;
392+ }
393+ }
338394 }
339395 }
340396
@@ -478,34 +534,38 @@ export default function getSchema(provider) {
478534 const only = fn ( this ) ;
479535
480536 if ( this [ prop ] !== undefined ) {
481- const isAry = ARYS . includes ( prop ) ;
482- let length ;
483- let method ;
484- if ( only instanceof Set ) {
485- ( { size : length } = only ) ;
486- method = 'has' ;
487- } else {
488- ( { length } = only ) ;
489- method = 'includes' ;
490- }
491-
492- if ( isAry && ! this [ prop ] . every ( ( val ) => only [ method ] ( val ) ) ) {
493- if ( length ) {
494- this . invalidate ( `${ prop } can only contain ${ formatters . formatList ( [ ...only ] , { type : 'disjunction' } ) } ` ) ;
495- } else {
496- this . invalidate ( `${ prop } must be empty (no values are allowed)` ) ;
497- }
498- } else if ( ! isAry && ! only [ method ] ( this [ prop ] ) ) {
499- if ( length ) {
500- this . invalidate ( `${ prop } must be ${ formatters . formatList ( [ ...only ] , { type : 'disjunction' } ) } ` ) ;
501- } else {
502- this . invalidate ( `${ prop } must not be provided (no values are allowed)` ) ;
503- }
504- }
537+ this . #enum( prop , only ) ;
505538 }
506539 } ) ;
507540 }
508541
542+ #enum( prop , only ) {
543+ const isAry = ARYS . includes ( prop ) ;
544+ let length ;
545+ let method ;
546+ if ( only instanceof Set ) {
547+ ( { size : length } = only ) ;
548+ method = 'has' ;
549+ } else {
550+ ( { length } = only ) ;
551+ method = 'includes' ;
552+ }
553+
554+ if ( isAry && ! this [ prop ] . every ( ( val ) => only [ method ] ( val ) ) ) {
555+ if ( length ) {
556+ this . invalidate ( `${ prop } can only contain ${ formatters . formatList ( [ ...only ] , { type : 'disjunction' } ) } ` ) ;
557+ } else {
558+ this . invalidate ( `${ prop } must be empty (no values are allowed)` ) ;
559+ }
560+ } else if ( ! isAry && ! only [ method ] ( this [ prop ] ) ) {
561+ if ( length ) {
562+ this . invalidate ( `${ prop } must be ${ formatters . formatList ( [ ...only ] , { type : 'disjunction' } ) } ` ) ;
563+ } else {
564+ this . invalidate ( `${ prop } must not be provided (no values are allowed)` ) ;
565+ }
566+ }
567+ }
568+
509569 normalizeResponseTypes ( ) {
510570 this . response_types = this . response_types . map ( ( type ) => [ ...new Set ( type . split ( ' ' ) ) ] . sort ( ) . join ( ' ' ) ) ;
511571 }
@@ -602,11 +662,19 @@ export default function getSchema(provider) {
602662 const allowed = [ ...RECOGNIZED_METADATA , ...configuration . extraClientMetadata . properties ] ;
603663 Object . keys ( this ) . forEach ( ( prop ) => {
604664 if ( ! allowed . includes ( prop ) ) {
605- delete this [ prop ] ;
665+ this . #unset ( prop ) ;
606666 }
607667 } ) ;
608668 }
609669
670+ ensureStripChoices ( ) {
671+ Object . values ( CHOICES ) . forEach ( this . #unset, this ) ;
672+ }
673+
674+ #unset( prop ) {
675+ delete this [ prop ] ;
676+ }
677+
610678 scopes ( ) {
611679 if ( this . scope ) {
612680 const parsed = new Set ( this . scope . split ( ' ' ) ) ;
0 commit comments