33using ProtoBuf . Grpc . Internal ;
44using ProtoBuf . Meta ;
55using System ;
6+ using System . Collections . Generic ;
67using System . Reflection ;
78
89namespace ProtoBuf . Grpc . Reflection
@@ -25,67 +26,100 @@ public sealed class SchemaGenerator
2526 /// <summary>
2627 /// Get the .proto schema associated with a service contract
2728 /// </summary>
29+ /// <typeparam name="TService">The service type to generate schema for.</typeparam>
2830 /// <remarks>This API is considered experimental and may change slightly</remarks>
2931 public string GetSchema < TService > ( )
3032 => GetSchema ( typeof ( TService ) ) ;
3133
3234 /// <summary>
3335 /// Get the .proto schema associated with a service contract
3436 /// </summary>
35- /// <remarks>This API is considered experimental and may change slightly</remarks>
37+ /// <param name="contractType">The service type to generate schema for.</param>
38+ /// <remarks>This API is considered experimental and may change slightly.
39+ /// ATTENTION! although the 'GetSchema(params Type[] contractTypes)' covers also a case of 'GetSchema(Type contractType)',
40+ /// this method need to remain for backward compatibility for client which will get this updated version, without recompilation.
41+ /// Thus, this method mustn't be deleted.</remarks>
3642 public string GetSchema ( Type contractType )
43+ => GetSchema ( new [ ] { contractType } ) ;
44+
45+ /// <summary>
46+ /// Get the .proto schema associated with multiple service contracts
47+ /// </summary>
48+ /// <param name="contractTypes">Array (or params syntax) of service types to generate schema for.</param>
49+ /// <remarks>This API is considered experimental and may change slightly
50+ /// All types will be generated into single schema.
51+ /// All the shared classes the services use will be generated only once for all of them.</remarks>
52+ public string GetSchema ( params Type [ ] contractTypes )
3753 {
54+ string globalPackage = "" ;
55+ List < Service > services = new List < Service > ( ) ;
3856 var binderConfiguration = BinderConfiguration ?? BinderConfiguration . Default ;
3957 var binder = binderConfiguration . Binder ;
40- if ( ! binder . IsServiceContract ( contractType , out var name ) )
58+ foreach ( var contractType in contractTypes )
4159 {
42- throw new ArgumentException ( $ "Type '{ contractType . Name } ' is not a service contract", nameof ( contractType ) ) ;
43- }
60+ if ( ! binder . IsServiceContract ( contractType , out var name ) )
61+ {
62+ throw new ArgumentException ( $ "Type '{ contractType . Name } ' is not a service contract",
63+ nameof ( contractTypes ) ) ;
64+ }
4465
45- name = ServiceBinder . GetNameParts ( name , contractType , out var package ) ;
46- var service = new Service
47- {
48- Name = name
49- } ;
50- var ops = contractType . GetMethods ( BindingFlags . Public | BindingFlags . Instance ) ;
51- foreach ( var method in ops )
52- {
53- if ( method . DeclaringType == typeof ( object ) )
54- { /* skip */ }
55- else if ( ContractOperation . TryIdentifySignature ( method , binderConfiguration , out var op , null ) )
66+ name = ServiceBinder . GetNameParts ( name , contractType , out var package ) ;
67+ // currently we allow only services from same package, to be output to single proto file
68+ if ( ! string . IsNullOrEmpty ( globalPackage )
69+ && package != globalPackage )
5670 {
57- service . Methods . Add (
58- new ServiceMethod
59- {
60- Name = op . Name ,
61- InputType = ApplySubstitutes ( op . From ) ,
62- OutputType = ApplySubstitutes ( op . To ) ,
63- ClientStreaming = op . MethodType switch
64- {
65- MethodType . ClientStreaming => true ,
66- MethodType . DuplexStreaming => true ,
67- _ => false ,
68- } ,
69- ServerStreaming = op . MethodType switch
71+ throw new ArgumentException (
72+ $ "All services must be of the same package! '{ contractType . Name } ' is from package '{ package } ' while previous package: { globalPackage } ",
73+ nameof ( contractTypes ) ) ;
74+ }
75+ globalPackage = package ;
76+
77+ var service = new Service
78+ {
79+ Name = name
80+ } ;
81+ var ops = contractType . GetMethods ( BindingFlags . Public | BindingFlags . Instance ) ;
82+ foreach ( var method in ops )
83+ {
84+ if ( method . DeclaringType == typeof ( object ) )
85+ {
86+ /* skip */
87+ }
88+ else if ( ContractOperation . TryIdentifySignature ( method , binderConfiguration , out var op , null ) )
89+ {
90+ service . Methods . Add (
91+ new ServiceMethod
7092 {
71- MethodType . ServerStreaming => true ,
72- MethodType . DuplexStreaming => true ,
73- _ => false ,
74- } ,
75- }
76- ) ;
93+ Name = op . Name ,
94+ InputType = ApplySubstitutes ( op . From ) ,
95+ OutputType = ApplySubstitutes ( op . To ) ,
96+ ClientStreaming = op . MethodType switch
97+ {
98+ MethodType . ClientStreaming => true ,
99+ MethodType . DuplexStreaming => true ,
100+ _ => false ,
101+ } ,
102+ ServerStreaming = op . MethodType switch
103+ {
104+ MethodType . ServerStreaming => true ,
105+ MethodType . DuplexStreaming => true ,
106+ _ => false ,
107+ } ,
108+ }
109+ ) ;
110+ }
77111 }
112+
113+ service . Methods . Sort ( ( x , y ) => string . Compare ( x . Name , y . Name ) ) ; // make it predictable
114+ services . Add ( service ) ;
78115 }
79- service . Methods . Sort ( ( x , y ) => string . Compare ( x . Name , y . Name ) ) ; // make it predictable
116+
80117 var options = new SchemaGenerationOptions
81118 {
82119 Syntax = ProtoSyntax ,
83- Package = package ,
84- Services =
85- {
86- service
87- }
120+ Package = globalPackage ,
88121 } ;
122+ options . Services . AddRange ( services ) ;
89123
90124 var model = binderConfiguration . MarshallerCache . TryGetFactory < ProtoBufMarshallerFactory > ( ) ? . Model ?? RuntimeTypeModel . Default ;
91125 return model . GetSchema ( options ) ;
0 commit comments