public ServiceDetails(ProtoCatalog catalog, string ns, ServiceDescriptor desc, ServiceConfig grpcServiceConfig) { Catalog = catalog; Namespace = ns; ProtoPackage = desc.File.Package; DocLines = desc.Declaration.DocLines().ToList(); SnippetsNamespace = $"{ns}.Snippets"; UnitTestsNamespace = $"{ns}.Tests"; // Must come early; used by `MethodDetails.Create()` MethodGrpcConfigsByName = grpcServiceConfig?.MethodConfig .SelectMany(conf => conf.Name.Select(name => (name, conf))) .Where(x => x.name.Service == desc.FullName) .ToImmutableDictionary(x => $"{x.name.Service}/{x.name.Method}", x => x.conf) ?? ImmutableDictionary <string, MethodConfig> .Empty; ServiceFullName = desc.FullName; ServiceName = desc.Name; DocumentationName = desc.Name; // TODO: There may be a more suitable name than this. ProtoTyp = Typ.Manual(ns, desc.Name); GrpcClientTyp = Typ.Nested(ProtoTyp, $"{desc.Name}Client"); SettingsTyp = Typ.Manual(ns, $"{desc.Name}Settings"); BuilderTyp = Typ.Manual(ns, $"{desc.Name}ClientBuilder"); ClientAbstractTyp = Typ.Manual(ns, $"{desc.Name}Client"); ClientImplTyp = Typ.Manual(ns, $"{desc.Name}ClientImpl"); DefaultHost = desc.GetExtension(ClientExtensions.DefaultHost) ?? ""; DefaultPort = 443; // Hardcoded; this is not specifiable by proto annotation. var oauthScopes = desc.GetExtension(ClientExtensions.OauthScopes); DefaultScopes = string.IsNullOrEmpty(oauthScopes) ? Enumerable.Empty <string>() : oauthScopes.Split(',', ' '); Methods = desc.Methods.Select(x => MethodDetails.Create(this, x)).ToList(); SnippetsTyp = Typ.Manual(SnippetsNamespace, $"Generated{desc.Name}ClientSnippets"); StandaloneSnippetsTyp = Typ.Manual(SnippetsNamespace, $"Generated{desc.Name}ClientStandaloneSnippets"); SnippetsClientName = $"{desc.Name.ToLowerCamelCase()}Client"; UnitTestsTyp = Typ.Manual(UnitTestsNamespace, $"Generated{desc.Name}ClientTest"); NonStandardLro = NonStandardLroDetails.ForService(desc); }
public Definition(FileDescriptor fileDesc, MessageDescriptor msgDesc, string type, string nameField, CommonResource common, IEnumerable <string> patterns) { MsgDesc = msgDesc; FileName = fileDesc.Name; UnifiedResourceTypeName = type; var typeNameParts = type.Split('/'); if (typeNameParts.Length != 2) { throw new InvalidOperationException($"Invalid unified resource name: '{type}' used in message '{msgDesc?.Name}'"); } ShortName = typeNameParts[1]; FieldName = ShortName.ToLowerCamelCase(); NameField = string.IsNullOrEmpty(nameField) ? "name" : nameField; DocName = ShortName; Patterns = patterns.Select(x => new Pattern(x)).ToList(); if (patterns.Distinct().Count() != Patterns.Count) { throw new InvalidOperationException("All patterns must be unique within a resource-name."); } IsCommon = common != null; ResourceNameTyp = IsCommon ? Typ.Manual(common.CsharpNamespace, common.CsharpClassName) : Typ.Manual(fileDesc.CSharpNamespace(), $"{ShortName}Name"); ResourceParserTyp = ResourceNameTyp; IsUnparsed = false; }
public PackageModel(RestDescription discoveryDoc, Features features, PackageEnumStorage enumStorage) { _features = features; _discoveryDoc = discoveryDoc; ApiName = discoveryDoc.Name; // Note that spaces are removed first, because the Python code doesn't Pascal-case them. For example, // the "Cloud Memorystore for Memcached" API has a package name of "CloudMemorystoreforMemcached". // It's awful, but that's the way it works... ClassName = (discoveryDoc.CanonicalName ?? discoveryDoc.Name).Replace(" ", "").ToMemberName(); ApiVersion = discoveryDoc.Version; string versionNoDots = discoveryDoc.Version.Replace('.', '_'); var camelizedPackagePath = discoveryDoc.PackagePath is null ? "" : string.Join('.', discoveryDoc.PackagePath.Split('/').Select(part => part.ToUpperCamelCase())) + "."; PackageName = $"Google.Apis.{camelizedPackagePath}{ClassName}.{versionNoDots}"; DataModels = discoveryDoc.Schemas.ToReadOnlyList(pair => new DataModel(this, parent: null, name: pair.Key, schema: pair.Value)); // Populate the data model dictionary early, as methods and resources refer to the data model types. foreach (var dm in DataModels) { _dataModelsById[dm.Id] = dm; } Resources = discoveryDoc.Resources.ToReadOnlyList(pair => new ResourceModel(this, parent: null, pair.Key, pair.Value)); // TODO: Ordering? AuthScopes = (discoveryDoc.Auth?.Oauth2?.Scopes).ToReadOnlyList(pair => new AuthScope(pair.Key, pair.Value.Description)); ServiceTyp = Typ.Manual(PackageName, $"{ClassName}Service"); GenericBaseRequestTypDef = Typ.Manual(PackageName, $"{ClassName}BaseServiceRequest"); Methods = discoveryDoc.Methods.ToReadOnlyList(pair => new MethodModel(this, null, pair.Key, pair.Value)); PackageEnumStorage = enumStorage; }
public PackageModel(RestDescription discoveryDoc) { _discoveryDoc = discoveryDoc; ApiName = discoveryDoc.Name; // Note that spaces are removed first, because the Python code doesn't Pascal-case them. For example, // the "Cloud Memorystore for Memcached" API has a package name of "CloudMemorystoreforMemcached". // It's awful, but that's the way it works... ClassName = (discoveryDoc.CanonicalName ?? discoveryDoc.Name).Replace(" ", "").ToMemberName(); ServiceClassName = $"{ClassName}Service"; ApiVersion = discoveryDoc.Version; VersionNoDots = discoveryDoc.Version.Replace('.', '_'); PackageName = $"Google.Apis.{ClassName}.{VersionNoDots}"; DataModels = discoveryDoc.Schemas.ToReadOnlyList(pair => new DataModel(this, parent: null, name: pair.Key, schema: pair.Value)); // Populate the data model dictionary early, as methods and resources refer to the data model types. foreach (var dm in DataModels) { _dataModels[dm.Id] = dm; } Resources = discoveryDoc.Resources.ToReadOnlyList(pair => new ResourceModel(this, parent: null, pair.Key, pair.Value)); ApiFeatures = discoveryDoc.Features.ToReadOnlyList(); // TODO: Ordering? AuthScopes = (discoveryDoc.Auth?.Oauth2?.Scopes).ToReadOnlyList(pair => new AuthScope(pair.Key, pair.Value.Description)); ServiceTyp = Typ.Manual(PackageName, ServiceClassName); GenericBaseRequestTypDef = Typ.Manual(PackageName, $"{ClassName}BaseServiceRequest"); BaseRequestTyp = Typ.Generic(GenericBaseRequestTypDef, Typ.GenericParam("TResponse")); BaseUri = discoveryDoc.RootUrl + discoveryDoc.ServicePath; BasePath = discoveryDoc.ServicePath; BatchUri = discoveryDoc.RootUrl + discoveryDoc.BatchPath; BatchPath = discoveryDoc.BatchPath; Title = discoveryDoc.Title; Methods = discoveryDoc.Methods.ToReadOnlyList(pair => new MethodModel(this, null, pair.Key, pair.Value)); }
public ClassDeclarationSyntax GenerateBaseRequestClass(SourceFileContext ctx) { var cls = Class( Modifier.Public, Typ.Generic(Typ.Manual(PackageName, $"{ClassName}BaseServiceRequest"), Typ.GenericParam("TResponse")), ctx.Type(Typ.Generic(typeof(ClientServiceRequest <>), Typ.GenericParam("TResponse")))) .WithXmlDoc(XmlDoc.Summary($"A base abstract class for {ClassName} requests.")); var serviceParam = Parameter(ctx.Type <IClientService>(), "service"); var ctor = Ctor(Modifier.Protected, cls, BaseInitializer(serviceParam))(serviceParam) .WithBody() .WithXmlDoc(XmlDoc.Summary($"Constructs a new {ClassName}BaseServiceRequest instance.")); cls = cls.AddMembers(ctor); var parameters = _discoveryDoc.Parameters.Select(p => new ParameterModel(p.Key, p.Value)) .OrderBy(p => p.PropertyName) .ToList(); cls = cls.AddMembers(parameters.Select(p => p.GenerateProperty(ctx)).ToArray()); var initParameters = Method(Modifier.Protected | Modifier.Override, VoidType, "InitParameters")() .WithBlockBody( BaseExpression().Call("InitParameters")(), parameters.Select(p => p.GenerateInitializer(ctx)).ToArray()) .WithXmlDoc(XmlDoc.Summary($"Initializes {ClassName} parameter list.")); cls = cls.AddMembers(initParameters); return(cls); }
public ResourceModel(PackageModel package, ResourceModel parent, string name, RestResource discoveryResource) { Name = name; PropertyName = name.ToMemberName(); string className = name.ToClassName(package, parent?.Typ.Name) + "Resource"; Typ = parent is null?Typ.Manual(package.PackageName, className) : Typ.Nested(parent.Typ, className); Subresources = discoveryResource.Resources.ToReadOnlyList(pair => new ResourceModel(package, this, pair.Key, pair.Value)); Methods = discoveryResource.Methods.ToReadOnlyList(pair => new MethodModel(package, this, pair.Key, pair.Value)); }
public static CompilationUnitSyntax GenerateExtensions(SourceFileContext ctx, List <ServiceDetails> packageServiceDetails) { var ns = typeof(IServiceCollection).Namespace; var namespaceDeclaration = Namespace(ns); using (ctx.InNamespace(namespaceDeclaration)) { var cls = Class(Public | Static | Partial, Typ.Manual(ns, ClassName)) .WithXmlDoc(XmlDoc.Summary("Static class to provide extension methods to configure API clients.")); cls = cls.AddMembers(packageServiceDetails.Select(m => GenerateMethod(ctx, m)).ToArray()); namespaceDeclaration = namespaceDeclaration.AddMembers(cls); } return(ctx.CreateCompilationUnit(namespaceDeclaration)); }
public ServiceDetails(ProtoCatalog catalog, string ns, ServiceDescriptor desc, ServiceConfig grpcServiceConfig, Service serviceConfig, ApiTransports transports) { Catalog = catalog; Namespace = ns; ProtoPackage = desc.File.Package; PackageVersion = ProtoPackage.Split('.').FirstOrDefault(part => ApiVersionPattern.IsMatch(part)); DocLines = desc.Declaration.DocLines().ToList(); SnippetsNamespace = $"{ns}.Snippets"; UnitTestsNamespace = $"{ns}.Tests"; // Must come early; used by `MethodDetails.Create()` MethodGrpcConfigsByName = grpcServiceConfig?.MethodConfig .SelectMany(conf => conf.Name.Select(name => (name, conf))) .Where(x => x.name.Service == desc.FullName) .ToImmutableDictionary(x => $"{x.name.Service}/{x.name.Method}", x => x.conf) ?? ImmutableDictionary <string, MethodConfig> .Empty; ServiceFullName = desc.FullName; ServiceName = desc.Name; DocumentationName = desc.Name; // TODO: There may be a more suitable name than this. ProtoTyp = Typ.Manual(ns, desc.Name); GrpcClientTyp = Typ.Nested(ProtoTyp, $"{desc.Name}Client"); SettingsTyp = Typ.Manual(ns, $"{desc.Name}Settings"); BuilderTyp = Typ.Manual(ns, $"{desc.Name}ClientBuilder"); ClientAbstractTyp = Typ.Manual(ns, $"{desc.Name}Client"); ClientImplTyp = Typ.Manual(ns, $"{desc.Name}ClientImpl"); DefaultHost = desc.GetExtension(ClientExtensions.DefaultHost) ?? ""; // We need to account for regional default endpoints like "us-east1-pubsub.googleapis.com" DefaultHostServiceName = DefaultHost .Split('.', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?.Split('-', StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); DefaultPort = 443; // Hardcoded; this is not specifiable by proto annotation. var oauthScopes = desc.GetExtension(ClientExtensions.OauthScopes); DefaultScopes = string.IsNullOrEmpty(oauthScopes) ? Enumerable.Empty <string>() : oauthScopes.Split(',', ' '); Methods = desc.Methods.Select(x => MethodDetails.Create(this, x)).ToList(); ServiceSnippetsTyp = Typ.Manual(SnippetsNamespace, $"AllGenerated{desc.Name}ClientSnippets"); SnippetsTyp = Typ.Manual(SnippetsNamespace, $"Generated{desc.Name}ClientSnippets"); SnippetsClientName = $"{desc.Name.ToLowerCamelCase()}Client"; UnitTestsTyp = Typ.Manual(UnitTestsNamespace, $"Generated{desc.Name}ClientTest"); NonStandardLro = NonStandardLroDetails.ForService(desc); Mixins = serviceConfig?.Apis .Select(api => AvailableMixins.GetValueOrDefault(api.Name)) .Where(mixin => mixin is object) // Don't use mixins within the package that contains that mixin. .Where(mixin => mixin.GapicClientType.Namespace != ns) .ToList() ?? Enumerable.Empty <MixinDetails>(); Transports = transports; }
public DataModel(PackageModel package, DataModel parent, string name, JsonSchema schema) { _schema = schema; Name = name; Id = schema.Id; Package = package; Parent = parent; // TODO: Move this logic into Naming somehow, but noting that we *don't* escape keywords here. <sigh> string className = schema.Id is object && IsArray ? name + "Items" : name; className = className.ToUpperCamelCase(upperAfterDigit: null); if (className == parent?.Typ.Name) { className += "Schema"; } Typ = parent is null?Typ.Manual(Package.PackageName + ".Data", className) : Typ.Nested(parent.Typ, className); // We may get a JsonSchema for an array as a nested model. Just use the properties from schema.Items for simplicity. Properties = GetProperties(schema).ToReadOnlyList(pair => new DataPropertyModel(this, pair.Key, pair.Value)); }
public PackageModel(RestDescription discoveryDoc) { _discoveryDoc = discoveryDoc; ApiName = discoveryDoc.Name; ClassName = ToClassName(discoveryDoc.CanonicalName); ServiceClassName = $"{ClassName}Service"; ApiVersion = discoveryDoc.Version; PackageName = $"Google.Apis.{ClassName}.{ApiVersion}"; VersionNoDots = discoveryDoc.Version.Replace('.', '_'); Resources = discoveryDoc.Resources.ToReadOnlyList(pair => new ResourceModel(this, pair.Key, pair.Value)); Features = discoveryDoc.Features.ToReadOnlyList(); // TODO: Ordering? AuthScopes = (discoveryDoc.Auth?.Oauth2?.Scopes).ToReadOnlyList(pair => new AuthScope(pair.Key, pair.Value.Description)); ServiceTyp = Typ.Manual(PackageName, ServiceClassName); BaseUri = discoveryDoc.RootUrl + discoveryDoc.ServicePath; BasePath = discoveryDoc.ServicePath; BatchUri = discoveryDoc.RootUrl + discoveryDoc.BatchPath; BatchPath = discoveryDoc.BatchPath; Title = discoveryDoc.Title; // TODO: Add in the anonymous schemas from method definitions DataModels = discoveryDoc.Schemas.ToReadOnlyList(pair => new DataModel(this, pair.Key, pair.Value)); Methods = discoveryDoc.Methods.ToReadOnlyList(pair => new MethodModel(this, pair.Key, pair.Value)); }
private static ClassDeclarationSyntax GenerateClass(string ns, SourceFileContext ctx, IEnumerable <FileDescriptor> packageFileDescriptors) { var typ = Typ.Manual(ns, ClassName); var cls = Class(Internal | Static, typ) .WithXmlDoc(XmlDoc.Summary("Static class to provide common access to package-wide API metadata.")); var yieldStatements = packageFileDescriptors.Select(GenerateYieldStatement).ToArray(); var fileDescriptorMethod = Method(Private | Static, ctx.Type(Typ.Of <IEnumerable <FileDescriptor> >()), "GetFileDescriptors")() .WithBlockBody(yieldStatements); var apiMetadataType = ctx.Type <ApiMetadata>(); var property = AutoProperty(Internal | Static, apiMetadataType, PropertyName) .WithInitializer(New(apiMetadataType)(ns, IdentifierName(fileDescriptorMethod.Identifier))) .WithXmlDoc(XmlDoc.Summary("The ", apiMetadataType, " for services in this package.")); return(cls.AddMembers(property, fileDescriptorMethod)); YieldStatementSyntax GenerateYieldStatement(FileDescriptor descriptor) { var type = ctx.Type(ProtoTyp.OfReflectionClass(descriptor)); return(YieldStatement(SyntaxKind.YieldReturnStatement, type.Access("Descriptor"))); } }
private static IEnumerable <MemberDeclarationSyntax> LroPartialClasses(SourceFileContext ctx, ServiceDetails svc) { if (svc.Methods.Any(m => m is MethodDetails.StandardLro)) { // Emit partial class to give access to an LRO operations client. var grpcOuterCls = Class(Public | Static | Partial, svc.GrpcClientTyp.DeclaringTyp); using (ctx.InClass(grpcOuterCls)) { var grpcInnerClass = Class(Public | Partial, svc.GrpcClientTyp); using (ctx.InClass(grpcInnerClass)) { var callInvoker = Property(Private, ctx.TypeDontCare, "CallInvoker"); var opTyp = ctx.Type <Operations.OperationsClient>(); var createOperationsClientMethod = Method(Public | Virtual, opTyp, "CreateOperationsClient")() .WithBody(New(opTyp)(callInvoker)) .WithXmlDoc( XmlDoc.Summary("Creates a new instance of ", opTyp, " using the same call invoker as this client."), XmlDoc.Returns("A new Operations client for the same target as this client.") ); grpcInnerClass = grpcInnerClass.AddMembers(createOperationsClientMethod); } grpcOuterCls = grpcOuterCls.AddMembers(grpcInnerClass); } yield return(grpcOuterCls); } // Generate partial classes to delegate to other services handling operations if (svc.Methods.Any(m => m is MethodDetails.NonStandardLro)) { var operationServices = svc.Methods.OfType <MethodDetails.NonStandardLro>().Select(lro => lro.OperationService).Distinct().ToList(); // Emit partial class to give access to an LRO operations client. var grpcOuterCls = Class(Public | Static | Partial, svc.GrpcClientTyp.DeclaringTyp); using (ctx.InClass(grpcOuterCls)) { var grpcInnerClass = Class(Public | Partial, svc.GrpcClientTyp); using (ctx.InClass(grpcInnerClass)) { var callInvoker = Property(Private, ctx.TypeDontCare, "CallInvoker"); var opTyp = ctx.Type <Operations.OperationsClient>(); foreach (var operationService in operationServices) { var grpcClient = ctx.Type(Typ.Nested(Typ.Manual(ctx.Namespace, operationService), $"{operationService}Client")); var createOperationsClientMethod = Method(Public | Virtual, opTyp, $"CreateOperationsClientFor{operationService}")() .WithBody(grpcClient.Call("CreateOperationsClient")(callInvoker)) .WithXmlDoc( XmlDoc.Summary("Creates a new instance of ", opTyp, $" using the same call invoker as this client, delegating to {operationService}."), XmlDoc.Returns("A new Operations client for the same target as this client.") ); grpcInnerClass = grpcInnerClass.AddMembers(createOperationsClientMethod); } } grpcOuterCls = grpcOuterCls.AddMembers(grpcInnerClass); } yield return(grpcOuterCls); } // Generate partial classes for the operation-handling services if (svc.NonStandardLro is ServiceDetails.NonStandardLroDetails lroDetails) { // Emit partial class to give access to an LRO operations client. var grpcOuterCls = Class(Public | Static | Partial, svc.GrpcClientTyp.DeclaringTyp); using (ctx.InClass(grpcOuterCls)) { var grpcInnerClass = Class(Public | Partial, svc.GrpcClientTyp); using (ctx.InClass(grpcInnerClass)) { var callInvoker = Parameter(ctx.Type <CallInvoker>(), "callInvoker"); var request = Parameter(ctx.TypeDontCare, "request"); var response = Parameter(ctx.TypeDontCare, "response"); var opTyp = ctx.Type <Operations.OperationsClient>(); var forwardingCallInvoker = Local(ctx.Type <CallInvoker>(), "forwardingCallInvoker"); var createOperationsClientMethod = Method(Internal | Static, opTyp, "CreateOperationsClient")(callInvoker) .WithBody( forwardingCallInvoker.WithInitializer( // Note: can't use Typ.Of<ForwardingCallInvoker<GetOperationRequest>> as it's a static class. ctx.Type(Typ.Generic(Typ.Of(typeof(ForwardingCallInvoker <>)), Typ.Of <GetOperationRequest>())).Call("Create")( callInvoker, "/google.longrunning.Operations/GetOperation", Property(Private, ctx.TypeDontCare, $"__Method_{lroDetails.PollingMethod.Name}"), ctx.Type(lroDetails.PollingRequestTyp).Access("ParseLroRequest"), // Method group conversion Lambda(request, response)(response.Call("ToLroResponse")(request.Access("Name"))) )), Return(New(opTyp)(forwardingCallInvoker))) .WithXmlDoc( XmlDoc.Summary( "Creates a new instance of ", opTyp, "using the specified call invoker, but ", $"redirecting Google.LongRunning RPCs to {lroDetails.Service.Name} RPCs."), XmlDoc.Returns("A new Operations client for the same target as this client.") ); grpcInnerClass = grpcInnerClass.AddMembers(createOperationsClientMethod); } grpcOuterCls = grpcOuterCls.AddMembers(grpcInnerClass); } yield return(grpcOuterCls); } }
public ClassDeclarationSyntax GenerateServiceClass(SourceFileContext ctx) { var cls = Class(Modifier.Public, ServiceTyp, ctx.Type <BaseClientService>()).WithXmlDoc(XmlDoc.Summary($"The {ClassName} Service.")); using (ctx.InClass(cls)) { var discoveryVersionTyp = Typ.Manual("Google.Apis.Discovery", "DiscoveryVersion"); var version = Field(Modifier.Public | Modifier.Const, ctx.Type <string>(), "Version") .WithInitializer(ApiVersion) .WithXmlDoc(XmlDoc.Summary("The API version.")); var discoveryVersion = Field(Modifier.Public | Modifier.Static, ctx.Type(discoveryVersionTyp), "DiscoveryVersionUsed") .WithInitializer(ctx.Type(discoveryVersionTyp).Access(nameof(DiscoveryVersion.Version_1_0))) .WithXmlDoc(XmlDoc.Summary("The discovery version used to generate this service.")); var parameterlessCtor = Ctor(Modifier.Public, cls, ThisInitializer(New(ctx.Type <BaseClientService.Initializer>())()))() .WithBody() .WithXmlDoc(XmlDoc.Summary("Constructs a new service.")); var initializerParam = Parameter(ctx.Type <BaseClientService.Initializer>(), "initializer"); var featuresArrayInitializer = ApiFeatures.Any() ? NewArray(ctx.ArrayType(Typ.Of <string[]>()))(ApiFeatures.ToArray()) : NewArray(ctx.ArrayType(Typ.Of <string[]>()), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0))); var features = Property(Modifier.Public | Modifier.Override, ctx.Type <IList <string> >(), "Features") .WithGetBody(featuresArrayInitializer) .WithXmlDoc(XmlDoc.Summary("Gets the service supported features.")); var nameProperty = Property(Modifier.Public | Modifier.Override, ctx.Type <string>(), "Name") .WithGetBody(ApiName) .WithXmlDoc(XmlDoc.Summary("Gets the service name.")); // Note: the following 4 properties have special handling post-generation, in terms // of adding the #if directives in. var baseUri = Property(Modifier.Public | Modifier.Override, ctx.Type <string>(), "BaseUri") .WithGetBody(IdentifierName("BaseUriOverride").NullCoalesce(BaseUri)) .WithXmlDoc(XmlDoc.Summary("Gets the service base URI.")); var basePath = Property(Modifier.Public | Modifier.Override, ctx.Type <string>(), "BasePath") .WithGetBody(BasePath) .WithXmlDoc(XmlDoc.Summary("Gets the service base path.")); var batchUri = Property(Modifier.Public | Modifier.Override, ctx.Type <string>(), "BatchUri") .WithGetBody(BatchUri) .WithXmlDoc(XmlDoc.Summary("Gets the batch base URI; ", XmlDoc.C("null"), " if unspecified.")); var batchPath = Property(Modifier.Public | Modifier.Override, ctx.Type <string>(), "BatchPath") .WithGetBody(BatchPath) .WithXmlDoc(XmlDoc.Summary("Gets the batch base path; ", XmlDoc.C("null"), " if unspecified.")); var resourceProperties = Resources.Select(resource => resource.GenerateProperty(ctx)).ToArray(); var parameterizedCtor = Ctor(Modifier.Public, cls, BaseInitializer(initializerParam))(initializerParam) .WithBlockBody(resourceProperties.Zip(Resources).Select(pair => pair.First.Assign(New(ctx.Type(pair.Second.Typ))(This))).ToArray()) .WithXmlDoc( XmlDoc.Summary("Constructs a new service."), XmlDoc.Param(initializerParam, "The service initializer.")); cls = cls.AddMembers(version, discoveryVersion, parameterlessCtor, parameterizedCtor, features, nameProperty, baseUri, basePath, batchUri, batchPath); if (AuthScopes.Any()) { var scopeClass = Class(Modifier.Public, Typ.Manual(PackageName, "Scope")) .WithXmlDoc(XmlDoc.Summary($"Available OAuth 2.0 scopes for use with the {Title}.")); using (ctx.InClass(scopeClass)) { foreach (var scope in AuthScopes) { var field = Field(Modifier.Public | Modifier.Static, ctx.Type <string>(), scope.Name) .WithInitializer(scope.Value) .WithXmlDoc(XmlDoc.Summary(scope.Description)); scopeClass = scopeClass.AddMembers(field); } } var scopeConstantsClass = Class(Modifier.Public | Modifier.Static, Typ.Manual(PackageName, "ScopeConstants")) .WithXmlDoc(XmlDoc.Summary($"Available OAuth 2.0 scope constants for use with the {Title}.")); using (ctx.InClass(scopeConstantsClass)) { foreach (var scope in AuthScopes) { var field = Field(Modifier.Public | Modifier.Const, ctx.Type <string>(), scope.Name) .WithInitializer(scope.Value) .WithXmlDoc(XmlDoc.Summary(scope.Description)); scopeConstantsClass = scopeConstantsClass.AddMembers(field); } } cls = cls.AddMembers(scopeClass, scopeConstantsClass); } // TODO: Find an example of this... foreach (var method in Methods) { cls = cls.AddMembers(method.GenerateDeclarations(ctx).ToArray()); } cls = cls.AddMembers(resourceProperties); } return(cls); }
private static IEnumerable <ResultFile> GeneratePackage(string ns, IEnumerable <FileDescriptor> packageFileDescriptors, ProtoCatalog catalog, IClock clock, ServiceConfig grpcServiceConfig, Service serviceConfig, List <ServiceDetails> allServiceDetails, ApiTransports transports) { var clientPathPrefix = $"{ns}{Path.DirectorySeparatorChar}"; var serviceSnippetsPathPrefix = $"{ns}.Snippets{Path.DirectorySeparatorChar}"; var snippetsPathPrefix = $"{ns}.GeneratedSnippets{Path.DirectorySeparatorChar}"; var unitTestsPathPrefix = $"{ns}.Tests{Path.DirectorySeparatorChar}"; bool hasLro = false; HashSet <string> mixins = new HashSet <string>(); bool hasContent = false; var packageServiceDetails = new List <ServiceDetails>(); HashSet <string> allResourceNameClasses = new HashSet <string>(); HashSet <string> duplicateResourceNameClasses = new HashSet <string>(); IList <Snippet> snippets = new List <Snippet>(); IEnumerable <Typ> packageTyps = packageFileDescriptors.SelectMany( fileDescriptor => fileDescriptor.Services.Select(serv => Typ.Manual(ns, serv.Name)) .Union(fileDescriptor.MessageTypes.Select(msg => Typ.Manual(ns, msg.Name))) .Union(fileDescriptor.EnumTypes.Select(e => Typ.Manual(ns, e.Name, isEnum: true)))); var seenPaginatedResponseTyps = new HashSet <Typ>(); foreach (var fileDesc in packageFileDescriptors) { foreach (var service in fileDesc.Services) { // Generate settings and client code for requested package. var serviceDetails = new ServiceDetails(catalog, ns, service, grpcServiceConfig, serviceConfig, transports); packageServiceDetails.Add(serviceDetails); var ctx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var code = ServiceCodeGenerator.Generate(ctx, serviceDetails, seenPaginatedResponseTyps); var filename = $"{clientPathPrefix}{serviceDetails.ClientAbstractTyp.Name}.g.cs"; yield return(new ResultFile(filename, code)); // Generate service-per-file snippets for the service // TODO: Consider removing this once we have integrated the snippet-per-file snippets // with docs generation. var serviceSnippetsCtx = SourceFileContext.CreateUnaliased( clock, s_wellknownNamespaceAliases, s_avoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport: true); var serviceSnippetsCode = SnippetCodeGenerator.Generate(serviceSnippetsCtx, serviceDetails); var serviceSnippetsFilename = $"{serviceSnippetsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Snippets.g.cs"; yield return(new ResultFile(serviceSnippetsFilename, serviceSnippetsCode)); // Generate snippet-per-file snippets for the service // TODO: Integrate snippet-per-file snippets with docs generation. foreach (var snippetGenerator in SnippetCodeGenerator.SnippetsGenerators(serviceDetails)) { var snippetCtx = SourceFileContext.CreateUnaliased( clock, s_wellknownNamespaceAliases, s_avoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport: false); var(snippetCode, snippetMetadata) = snippetGenerator.Generate(snippetCtx); snippetMetadata.File = $"{serviceDetails.ClientAbstractTyp.Name}.{snippetGenerator.SnippetMethodName}Snippet.g.cs"; var snippetFile = $"{snippetsPathPrefix}{snippetMetadata.File}"; snippets.Add(snippetMetadata); yield return(new ResultFile(snippetFile, snippetCode)); } // Generate unit tests for the the service. var unitTestCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var unitTestCode = UnitTestCodeGeneration.Generate(unitTestCtx, serviceDetails); var unitTestFilename = $"{unitTestsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Test.g.cs"; yield return(new ResultFile(unitTestFilename, unitTestCode)); // Record whether LRO/mixins are used. hasLro |= serviceDetails.Methods.Any(x => x is MethodDetails.Lro); foreach (var mixin in serviceDetails.Mixins) { mixins.Add(mixin.GrpcServiceName); } hasContent = true; } var resCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var(resCode, resCodeClassCount) = ResourceNamesGenerator.Generate(catalog, resCtx, fileDesc); // Only produce an output file if it contains >0 [partial] classes. if (resCodeClassCount > 0) { // Keep track of the resource names, to spot duplicates var resourceNameClasses = catalog.GetResourceDefsByFile(fileDesc) .Where(def => def.HasNotWildcard && !def.IsCommon) .Select(def => def.ResourceNameTyp.Name); foreach (var resourceNameClass in resourceNameClasses) { if (!allResourceNameClasses.Add(resourceNameClass)) { duplicateResourceNameClasses.Add(resourceNameClass); } } // Yield the file to be generated. var filenamePrefix = Path.GetFileNameWithoutExtension(fileDesc.Name).ToUpperCamelCase(); var resFilename = $"{clientPathPrefix}{filenamePrefix}ResourceNames.g.cs"; yield return(new ResultFile(resFilename, resCode)); hasContent = true; } var lroAdaptationCtx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var(lroCode, lroClassCount) = LroAdaptationGenerator.Generate(catalog, lroAdaptationCtx, fileDesc); if (lroClassCount > 0) { var filenamePrefix = Path.GetFileNameWithoutExtension(fileDesc.Name).ToUpperCamelCase(); var lroAdaptationFilename = $"{clientPathPrefix}{filenamePrefix}LroAdaptation.g.cs"; yield return(new ResultFile(lroAdaptationFilename, lroCode)); hasContent = true; } } // Now we've processed all the files, check for duplicate resource names. if (duplicateResourceNameClasses.Count > 0) { throw new InvalidOperationException($"The following resource name classes were created multiple times: {string.Join(", ", duplicateResourceNameClasses)}"); } // Only output csproj's and snippet metadata if there is any other generated content. // When processing a (proto) package without any services there will be no generated content. if (hasContent) { // Generate client csproj. var csprojContent = CsProjGenerator.GenerateClient(hasLro, mixins); var csprojFilename = $"{clientPathPrefix}{ns}.csproj"; yield return(new ResultFile(csprojFilename, csprojContent)); // If we only generated resources, we don't need to generate all of these. if (packageServiceDetails.Count > 0) { allServiceDetails.AddRange(packageServiceDetails); // Generate the package-wide API metadata var ctx = SourceFileContext.CreateFullyAliased(clock, s_wellknownNamespaceAliases); var packageApiMetadataContent = PackageApiMetadataGenerator.GeneratePackageApiMetadata(ns, ctx, packageFileDescriptors); var packageApiMetadataFilename = $"{clientPathPrefix}{PackageApiMetadataGenerator.FileName}"; yield return(new ResultFile(packageApiMetadataFilename, packageApiMetadataContent)); // Generate snippets csproj. var serviceSnippetsCsprojContent = CsProjGenerator.GenerateSnippets(ns); var serviceSnippetsCsProjFilename = $"{serviceSnippetsPathPrefix}{ns}.Snippets.csproj"; yield return(new ResultFile(serviceSnippetsCsProjFilename, serviceSnippetsCsprojContent)); // Generate dependency injection extension methods var dependencyInjectionExtensionsContent = ServiceCollectionExtensionsGenerator.GenerateExtensions(ctx, packageServiceDetails); var dependencyInjectionExtensionsFilename = $"{clientPathPrefix}{ServiceCollectionExtensionsGenerator.FileName}"; yield return(new ResultFile(dependencyInjectionExtensionsFilename, dependencyInjectionExtensionsContent)); // Generate snippet metadata (only for snippet-per-file snippets). // All services in this package have the same package information, namespace etc. so, we // just pick the first one var serviceDetails = packageServiceDetails.First(); var snippetIndexJsonContent = SnippetCodeGenerator.GenerateSnippetIndexJson(snippets, serviceDetails); yield return(new ResultFile($"{snippetsPathPrefix}snippet_metadata_{serviceDetails.ProtoPackage}.json", snippetIndexJsonContent)); // Generate snippet-per-file snippets csproj. var snippetsCsprojContent = CsProjGenerator.GenerateSnippets(ns); var snippetsCsProjFilename = $"{snippetsPathPrefix}{ns}.GeneratedSnippets.csproj"; yield return(new ResultFile(snippetsCsProjFilename, snippetsCsprojContent)); // Generate unit-tests csproj. var unitTestsCsprojContent = CsProjGenerator.GenerateUnitTests(ns); var unitTestsCsprojFilename = $"{unitTestsPathPrefix}{ns}.Tests.csproj"; yield return(new ResultFile(unitTestsCsprojFilename, unitTestsCsprojContent)); } } }