11import {
2+ isIntrinsicType ,
23 validateDecoratorNotOnType ,
34 validateDecoratorTarget ,
45 validateDecoratorTargetIntrinsic ,
56} from "../core/decorator-utils.js" ;
67import {
8+ StdTypeName ,
79 getDiscriminatedUnion ,
810 getTypeName ,
11+ ignoreDiagnostics ,
12+ reportDeprecated ,
913 validateDecoratorUniqueOnNode ,
1014} from "../core/index.js" ;
1115import { createDiagnostic , reportDiagnostic } from "../core/messages.js" ;
@@ -208,6 +212,14 @@ export function $format(context: DecoratorContext, target: Scalar | ModelPropert
208212 if ( ! validateDecoratorTargetIntrinsic ( context , target , "@format" , [ "string" , "bytes" ] ) ) {
209213 return ;
210214 }
215+ const targetType = getPropertyType ( target ) ;
216+ if ( targetType . kind === "Scalar" && isIntrinsicType ( context . program , targetType , "bytes" ) ) {
217+ reportDeprecated (
218+ context . program ,
219+ "Using `@format` on a bytes scalar is deprecated. Use `@encode` instead. https://github.com/microsoft/typespec/issues/1873" ,
220+ target
221+ ) ;
222+ }
211223
212224 context . program . stateMap ( formatValuesKey ) . set ( target , format ) ;
213225}
@@ -227,7 +239,7 @@ export function $pattern(
227239) {
228240 validateDecoratorUniqueOnNode ( context , target , $pattern ) ;
229241
230- if ( ! validateDecoratorTargetIntrinsic ( context , target , "@pattern" , "string" ) ) {
242+ if ( ! validateDecoratorTargetIntrinsic ( context , target , "@pattern" , [ "string" ] ) ) {
231243 return ;
232244 }
233245
@@ -250,7 +262,7 @@ export function $minLength(
250262 validateDecoratorUniqueOnNode ( context , target , $minLength ) ;
251263
252264 if (
253- ! validateDecoratorTargetIntrinsic ( context , target , "@minLength" , "string" ) ||
265+ ! validateDecoratorTargetIntrinsic ( context , target , "@minLength" , [ "string" ] ) ||
254266 ! validateRange ( context , minLength , getMaxLength ( context . program , target ) )
255267 ) {
256268 return ;
@@ -275,7 +287,7 @@ export function $maxLength(
275287 validateDecoratorUniqueOnNode ( context , target , $maxLength ) ;
276288
277289 if (
278- ! validateDecoratorTargetIntrinsic ( context , target , "@maxLength" , "string" ) ||
290+ ! validateDecoratorTargetIntrinsic ( context , target , "@maxLength" , [ "string" ] ) ||
279291 ! validateRange ( context , getMinLength ( context . program , target ) , maxLength )
280292 ) {
281293 return ;
@@ -523,7 +535,7 @@ const secretTypesKey = createStateSymbol("secretTypes");
523535export function $secret ( context : DecoratorContext , target : Scalar | ModelProperty ) {
524536 validateDecoratorUniqueOnNode ( context , target , $secret ) ;
525537
526- if ( ! validateDecoratorTargetIntrinsic ( context , target , "@secret" , "string" ) ) {
538+ if ( ! validateDecoratorTargetIntrinsic ( context , target , "@secret" , [ "string" ] ) ) {
527539 return ;
528540 }
529541 context . program . stateMap ( secretTypesKey ) . set ( target , true ) ;
@@ -533,6 +545,107 @@ export function isSecret(program: Program, target: Type): boolean | undefined {
533545 return program . stateMap ( secretTypesKey ) . get ( target ) ;
534546}
535547
548+ export type DateTimeKnownEncoding = "rfc3339" | "rfc7231" | "unixTimeStamp" ;
549+ export type DurationKnownEncoding = "ISO8601" | "seconds" ;
550+ export type BytesKnownEncoding = "base64" | "base64url" ;
551+ export interface EncodeData {
552+ encoding : DateTimeKnownEncoding | DurationKnownEncoding | string ;
553+ type : Scalar ;
554+ }
555+
556+ const encodeKey = createStateSymbol ( "encode" ) ;
557+ export function $encode (
558+ context : DecoratorContext ,
559+ target : Scalar | ModelProperty ,
560+ encoding : string | EnumMember ,
561+ encodeAs ?: Scalar
562+ ) {
563+ validateDecoratorUniqueOnNode ( context , target , $encode ) ;
564+
565+ const encodeData : EncodeData = {
566+ encoding : typeof encoding === "string" ? encoding : encoding . value ?. toString ( ) ?? encoding . name ,
567+ type : encodeAs ?? context . program . checker . getStdType ( "string" ) ,
568+ } ;
569+ const targetType = getPropertyType ( target ) ;
570+ if ( targetType . kind !== "Scalar" ) {
571+ return ;
572+ }
573+ validateEncodeData ( context , targetType , encodeData ) ;
574+ context . program . stateMap ( encodeKey ) . set ( target , encodeData ) ;
575+ }
576+
577+ function validateEncodeData ( context : DecoratorContext , target : Scalar , encodeData : EncodeData ) {
578+ function check ( validTargets : StdTypeName [ ] , validEncodeTypes : StdTypeName [ ] ) {
579+ const checker = context . program . checker ;
580+ const isTargetValid = validTargets . some ( ( validTarget ) => {
581+ return ignoreDiagnostics (
582+ checker . isTypeAssignableTo (
583+ target . projectionBase ?? target ,
584+ checker . getStdType ( validTarget ) ,
585+ target
586+ )
587+ ) ;
588+ } ) ;
589+
590+ if ( ! isTargetValid ) {
591+ reportDiagnostic ( context . program , {
592+ code : "invalid-encode" ,
593+ messageId : "wrongType" ,
594+ format : {
595+ encoding : encodeData . encoding ,
596+ type : getTypeName ( target ) ,
597+ expected : validTargets . join ( ", " ) ,
598+ } ,
599+ target : context . decoratorTarget ,
600+ } ) ;
601+ }
602+ const isEncodingTypeValid = validEncodeTypes . some ( ( validEncoding ) => {
603+ return ignoreDiagnostics (
604+ checker . isTypeAssignableTo (
605+ encodeData . type . projectionBase ?? encodeData . type ,
606+ checker . getStdType ( validEncoding ) ,
607+ target
608+ )
609+ ) ;
610+ } ) ;
611+
612+ if ( ! isEncodingTypeValid ) {
613+ reportDiagnostic ( context . program , {
614+ code : "invalid-encode" ,
615+ messageId : "wrongEncodingType" ,
616+ format : {
617+ encoding : encodeData . encoding ,
618+ type : getTypeName ( target ) ,
619+ expected : validEncodeTypes . join ( ", " ) ,
620+ } ,
621+ target : context . decoratorTarget ,
622+ } ) ;
623+ }
624+ }
625+
626+ switch ( encodeData . encoding ) {
627+ case "rfc3339" :
628+ return check ( [ "utcDateTime" , "offsetDateTime" ] , [ "string" ] ) ;
629+ case "rfc7231" :
630+ return check ( [ "utcDateTime" , "offsetDateTime" ] , [ "string" ] ) ;
631+ case "unixTimeStamp" :
632+ return check ( [ "utcDateTime" ] , [ "string" ] ) ;
633+ case "seconds" :
634+ return check ( [ "duration" ] , [ "numeric" ] ) ;
635+ case "base64" :
636+ return check ( [ "bytes" ] , [ "string" ] ) ;
637+ case "base64url" :
638+ return check ( [ "bytes" ] , [ "string" ] ) ;
639+ }
640+ }
641+
642+ export function getEncode (
643+ program : Program ,
644+ target : Scalar | ModelProperty
645+ ) : EncodeData | undefined {
646+ return program . stateMap ( encodeKey ) . get ( target ) ;
647+ }
648+
536649// -- @visibility decorator ---------------------
537650
538651const visibilitySettingsKey = createStateSymbol ( "visibilitySettings" ) ;
0 commit comments