@@ -27,6 +27,8 @@ internal interface IMessagePackServer
27
27
IAsyncEnumerable < UnionBaseClass > GetAsyncEnumerableOfUnionType ( CancellationToken cancellationToken ) ;
28
28
29
29
Task < bool > IsExtensionArgNonNull ( CustomExtensionType extensionValue ) ;
30
+
31
+ Task < string ? > GetAPropertyFromStruct ( [ MessagePackFormatter ( typeof ( ProprietaryStructFormatter ) ) ] ProprietaryStruct value , CancellationToken cancellationToken ) ;
30
32
}
31
33
32
34
protected override Type FormatterExceptionType => typeof ( MessagePackSerializationException ) ;
@@ -384,6 +386,23 @@ public async Task VerboseLoggingDoesNotFailWhenArgsDoNotDeserializePrimitively(b
384
386
Assert . True ( await clientProxy . IsExtensionArgNonNull ( new CustomExtensionType ( ) ) ) ;
385
387
}
386
388
389
+ [ Fact ]
390
+ public async Task FormatterOnParameter_Interface ( )
391
+ {
392
+ IMessagePackServer clientProxy = this . clientRpc . Attach < IMessagePackServer > ( ) ;
393
+ const string expected = "Beehive" ;
394
+ string ? actual = await clientProxy . GetAPropertyFromStruct ( new ProprietaryStruct { A = expected } , this . TimeoutToken ) ;
395
+ Assert . Equal ( expected , actual ) ;
396
+ }
397
+
398
+ [ Fact ]
399
+ public async Task FormatterOnParameter_NoInterface ( )
400
+ {
401
+ const string expected = "Beehive" ;
402
+ string ? actual = await this . clientRpc . InvokeWithCancellationAsync < string ? > ( nameof ( MessagePackServer . GetAPropertyFromStructNoInterface ) , [ expected ] , this . TimeoutToken ) ;
403
+ Assert . Equal ( expected , actual ) ;
404
+ }
405
+
387
406
protected override void InitializeFormattersAndHandlers (
388
407
Stream serverStream ,
389
408
Stream clientStream ,
@@ -409,6 +428,14 @@ protected override void InitializeFormattersAndHandlers(
409
428
: new LengthHeaderMessageHandler ( clientStream , clientStream , clientMessageFormatter ) ;
410
429
}
411
430
431
+ /// <summary>
432
+ /// A struct that intentionally uses no MessagePack attributes so that a custom formatter will have to be provided.
433
+ /// </summary>
434
+ internal struct ProprietaryStruct
435
+ {
436
+ public string ? A { get ; set ; }
437
+ }
438
+
412
439
[ MessagePackObject ]
413
440
[ Union ( 0 , typeof ( UnionDerivedClass ) ) ]
414
441
public abstract class UnionBaseClass
@@ -512,6 +539,16 @@ public async IAsyncEnumerable<UnionBaseClass> GetAsyncEnumerableOfUnionType([Enu
512
539
}
513
540
514
541
public Task < bool > IsExtensionArgNonNull ( CustomExtensionType extensionValue ) => Task . FromResult ( extensionValue is not null ) ;
542
+
543
+ public Task < string ? > GetAPropertyFromStruct ( ProprietaryStruct value , CancellationToken cancellationToken )
544
+ {
545
+ return Task . FromResult ( value . A ) ;
546
+ }
547
+
548
+ public Task < string ? > GetAPropertyFromStructNoInterface ( [ MessagePackFormatter ( typeof ( ProprietaryStructFormatter ) ) ] ProprietaryStruct value , CancellationToken cancellationToken )
549
+ {
550
+ return Task . FromResult ( value . A ) ;
551
+ }
515
552
}
516
553
517
554
private class DelayedFlushingHandler : LengthHeaderMessageHandler , IControlledFlushHandler
@@ -532,4 +569,17 @@ protected override async ValueTask FlushAsync(CancellationToken cancellationToke
532
569
await base . FlushAsync ( cancellationToken ) ;
533
570
}
534
571
}
572
+
573
+ private class ProprietaryStructFormatter : IMessagePackFormatter < ProprietaryStruct >
574
+ {
575
+ public ProprietaryStruct Deserialize ( ref MessagePackReader reader , MessagePackSerializerOptions options )
576
+ {
577
+ return new ProprietaryStruct { A = reader . ReadString ( ) } ;
578
+ }
579
+
580
+ public void Serialize ( ref MessagePackWriter writer , ProprietaryStruct value , MessagePackSerializerOptions options )
581
+ {
582
+ writer . Write ( value . A ) ;
583
+ }
584
+ }
535
585
}
0 commit comments