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);
        }
Exemplo n.º 2
0
            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;
            }
Exemplo n.º 3
0
        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;
        }
Exemplo n.º 4
0
        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));
        }
Exemplo n.º 5
0
        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;
        }
Exemplo n.º 9
0
        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));
        }
Exemplo n.º 10
0
 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));
 }
Exemplo n.º 11
0
        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")));
            }
        }
Exemplo n.º 12
0
        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);
            }
        }
Exemplo n.º 13
0
        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);
        }
Exemplo n.º 14
0
        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));
                }
            }
        }