3
3
using ProtoBuf . Grpc . Internal ;
4
4
using ProtoBuf . Meta ;
5
5
using System ;
6
+ using System . Collections . Generic ;
6
7
using System . Reflection ;
7
8
8
9
namespace ProtoBuf . Grpc . Reflection
@@ -25,67 +26,100 @@ public sealed class SchemaGenerator
25
26
/// <summary>
26
27
/// Get the .proto schema associated with a service contract
27
28
/// </summary>
29
+ /// <typeparam name="TService">The service type to generate schema for.</typeparam>
28
30
/// <remarks>This API is considered experimental and may change slightly</remarks>
29
31
public string GetSchema < TService > ( )
30
32
=> GetSchema ( typeof ( TService ) ) ;
31
33
32
34
/// <summary>
33
35
/// Get the .proto schema associated with a service contract
34
36
/// </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>
36
42
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 )
37
53
{
54
+ string globalPackage = "" ;
55
+ List < Service > services = new List < Service > ( ) ;
38
56
var binderConfiguration = BinderConfiguration ?? BinderConfiguration . Default ;
39
57
var binder = binderConfiguration . Binder ;
40
- if ( ! binder . IsServiceContract ( contractType , out var name ) )
58
+ foreach ( var contractType in contractTypes )
41
59
{
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
+ }
44
65
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 )
56
70
{
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
70
92
{
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
+ }
77
111
}
112
+
113
+ service . Methods . Sort ( ( x , y ) => string . Compare ( x . Name , y . Name ) ) ; // make it predictable
114
+ services . Add ( service ) ;
78
115
}
79
- service . Methods . Sort ( ( x , y ) => string . Compare ( x . Name , y . Name ) ) ; // make it predictable
116
+
80
117
var options = new SchemaGenerationOptions
81
118
{
82
119
Syntax = ProtoSyntax ,
83
- Package = package ,
84
- Services =
85
- {
86
- service
87
- }
120
+ Package = globalPackage ,
88
121
} ;
122
+ options . Services . AddRange ( services ) ;
89
123
90
124
var model = binderConfiguration . MarshallerCache . TryGetFactory < ProtoBufMarshallerFactory > ( ) ? . Model ?? RuntimeTypeModel . Default ;
91
125
return model . GetSchema ( options ) ;
0 commit comments