コード例 #1
0
 /// <summary>
 /// Constructs a new instance for a given service.
 /// </summary>
 /// <param name="serviceDescriptor">The protobuf descriptor for the service.</param>
 /// <param name="defaultEndpoint">The default endpoint to connect to.</param>
 /// <param name="defaultScopes">The default scopes for the service. Must not be null, and must not contain any null elements. May be empty.</param>
 /// <param name="supportsScopedJwts">Whether the service supports scoped JWTs as credentials.</param>
 /// <param name="transports">The transports supported by this service.</param>
 /// <param name="apiMetadata">The metadata for this API, including all of the services expected to be available at the same endpoint, and all associated protos.</param>
 public ServiceMetadata(ServiceDescriptor serviceDescriptor, string defaultEndpoint, IEnumerable <string> defaultScopes, bool supportsScopedJwts, ApiTransports transports, ApiMetadata apiMetadata)
 {
     ServiceDescriptor = GaxPreconditions.CheckNotNull(serviceDescriptor, nameof(serviceDescriptor));
     Name = serviceDescriptor.Name;
     GaxPreconditions.CheckArgument(Name.Length > 0, nameof(serviceDescriptor), "Service has an empty name");
     DefaultEndpoint = defaultEndpoint;
     DefaultScopes   = GaxPreconditions.CheckNotNull(defaultScopes, nameof(defaultScopes)).ToList().AsReadOnly();
     GaxPreconditions.CheckArgument(!DefaultScopes.Any(x => x == null), nameof(defaultScopes), "Scopes must not contain any null references");
     SupportsScopedJwts = supportsScopedJwts;
     Transports         = transports;
     ApiMetadata        = GaxPreconditions.CheckNotNull(apiMetadata, nameof(apiMetadata));
 }
コード例 #2
0
        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;
        }
コード例 #3
0
 internal static ServiceMetadata WithTransports(this ServiceMetadata metadata, ApiTransports transports) =>
 new ServiceMetadata(metadata.ServiceDescriptor, metadata.DefaultEndpoint, metadata.DefaultScopes, metadata.SupportsScopedJwts, transports, metadata.ApiMetadata);
コード例 #4
0
 private protected GrpcAdapter(ApiTransports supportedTransports)
 {
     _supportedTransports = supportedTransports;
 }
コード例 #5
0
 private void ProtoTestSingle(string testProtoName,
                              bool ignoreCsProj            = false, bool ignoreSnippets = false, bool ignoreUnitTests = false,
                              string grpcServiceConfigPath = null, string serviceConfigPath   = null, IEnumerable <string> commonResourcesConfigPaths = null, ApiTransports transports = ApiTransports.Grpc,
                              bool ignoreGapicMetadataFile = true, bool ignoreApiMetadataFile = true, bool ignoreServiceExtensionsFile = true) =>
 ProtoTestSingle(
     new[] { testProtoName },
     // The following three don't need to be customized for simple cases
     sourceDir: null,
     outputDir: null,
     package: null,
     ignoreCsProj,
     ignoreSnippets,
     ignoreUnitTests,
     grpcServiceConfigPath,
     serviceConfigPath,
     commonResourcesConfigPaths,
     transports,
     ignoreGapicMetadataFile,
     ignoreApiMetadataFile,
     ignoreServiceExtensionsFile
     );
コード例 #6
0
        private void ProtoTestSingle(IEnumerable <string> testProtoNames, string sourceDir = null, string outputDir = null, string package = null,
                                     bool ignoreCsProj            = false, bool ignoreSnippets = false, bool ignoreUnitTests = false,
                                     string grpcServiceConfigPath = null, string serviceConfigPath   = null, IEnumerable <string> commonResourcesConfigPaths = null, ApiTransports transports = ApiTransports.Grpc,
                                     bool ignoreGapicMetadataFile = true, bool ignoreApiMetadataFile = true, bool ignoreServiceExtensionsFile = true)
        {
            // Confirm each generated file is identical to the expected output.
            // Use `// TEST_START` and `// TEST_END` lines in the expected file to test subsets of output files.
            // Or include `// TEST_DISABLE` to disable testing of the entire file.
            sourceDir = sourceDir ?? testProtoNames.First();
            outputDir = outputDir ?? sourceDir;
            var protoPaths = testProtoNames.Select(x => Path.Combine("ProtoTests", sourceDir, $"{x}.proto"));

            package = package ?? $"testing.{sourceDir.ToLowerInvariant()}";

            var files = Run(protoPaths, package,
                            grpcServiceConfigPath, serviceConfigPath, commonResourcesConfigPaths, transports);

            // Check output is present.
            Assert.NotEmpty(files);

            // Write all output files to the temporary directory before validating any.
            // This makes it easier to see the complete set of outputs.
            foreach (var file in files)
            {
                var pathToWriteTo = Path.Combine(Invoker.ActualGeneratedFilesDir, outputDir, file.RelativePath);
                Directory.CreateDirectory(Path.GetDirectoryName(pathToWriteTo));
                File.WriteAllText(pathToWriteTo, file.Content);
            }

            // Verify each output file.
            foreach (var file in files)
            {
                if ((ignoreCsProj && file.RelativePath.EndsWith(".csproj")) ||
                    (ignoreSnippets && file.RelativePath.Contains($".Snippets{Path.DirectorySeparatorChar}")) ||
                    (ignoreSnippets && file.RelativePath.Contains($".GeneratedSnippets{Path.DirectorySeparatorChar}")) ||
                    (ignoreUnitTests && file.RelativePath.Contains($".Tests{Path.DirectorySeparatorChar}")) ||
                    (ignoreGapicMetadataFile && file.RelativePath.EndsWith("gapic_metadata.json")) ||
                    (ignoreApiMetadataFile && file.RelativePath.EndsWith(PackageApiMetadataGenerator.FileName)) ||
                    (ignoreServiceExtensionsFile && file.RelativePath.EndsWith(ServiceCollectionExtensionsGenerator.FileName)))
                {
                    continue;
                }
                var expectedFilePath = Path.Combine(Invoker.GeneratorTestsDir, "ProtoTests", outputDir, file.RelativePath);
                TextComparer.CompareText(expectedFilePath, file);
            }
        }
コード例 #7
0
        private IEnumerable <ResultFile> Run(IEnumerable <string> protoFilenames, string package,
                                             string grpcServiceConfigPath, string serviceConfigPath, IEnumerable <string> commonResourcesConfigPaths, ApiTransports transports)
        {
            var clock      = new FakeClock(new DateTime(2019, 1, 1));
            var protoPaths = protoFilenames.Select(x => Path.Combine(Invoker.GeneratorTestsDir, x));

            using (var desc = Invoker.TempFile())
            {
                var args = new[]
                {
                    "-o", desc.Path,
                    "--include_imports",
                    "--include_source_info",
                    $"-I{Invoker.CommonProtosDir}",
                    $"-I{Invoker.ProtobufDir}",
                    $"-I{Invoker.GeneratorTestsDir}",
                }.Concat(protoPaths);
                Invoker.Protoc(string.Join(" ", args));

                var descriptorBytes             = File.ReadAllBytes(desc.Path);
                FileDescriptorSet descriptorSet = FileDescriptorSet.Parser.ParseFrom(descriptorBytes);
                return(CodeGenerator.Generate(descriptorSet, package, clock, grpcServiceConfigPath, serviceConfigPath, commonResourcesConfigPaths, transports));
            }
        }
コード例 #8
0
        public static IEnumerable <ResultFile> Generate(FileDescriptorSet descriptorSet, string package, IClock clock,
                                                        string grpcServiceConfigPath, string serviceConfigPath, IEnumerable <string> commonResourcesConfigPaths, ApiTransports transports)
        {
            var descriptors     = descriptorSet.File;
            var filesToGenerate = descriptors.Where(x => x.Package == package).Select(x => x.Name).ToList();

            return(Generate(descriptors, filesToGenerate, clock, grpcServiceConfigPath, serviceConfigPath, commonResourcesConfigPaths, transports));
        }
コード例 #9
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));
                }
            }
        }
コード例 #10
0
        public static IEnumerable <ResultFile> Generate(IReadOnlyList <FileDescriptorProto> descriptorProtos, IEnumerable <string> filesToGenerate, IClock clock,
                                                        string grpcServiceConfigPath, string serviceConfigPath, IEnumerable <string> commonResourcesConfigPaths, ApiTransports transports)
        {
            var descriptors = FileDescriptor.BuildFromByteStrings(descriptorProtos.Select(proto => proto.ToByteString()), s_registry);
            // Load side-loaded configurations; both optional.
            var grpcServiceConfig = grpcServiceConfigPath is object?ServiceConfig.Parser.ParseJson(File.ReadAllText(grpcServiceConfigPath)) : null;

            var serviceConfig = ParseServiceConfigYaml(serviceConfigPath);

            var commonResourcesConfigs = commonResourcesConfigPaths != null?
                                         commonResourcesConfigPaths.Select(path => CommonResources.Parser.ParseJson(File.ReadAllText(path))) : null;

            // TODO: Multi-package support not tested.
            var filesToGenerateSet = filesToGenerate.ToHashSet();
            var byPackage          = descriptors.Where(x => filesToGenerateSet.Contains(x.Name)).GroupBy(x => x.Package).ToList();

            if (byPackage.Count == 0)
            {
                throw new InvalidOperationException("No packages specified to build.");
            }

            // Collect all service details here to emit one `gapic_metadata.json` file for multi-package situations (e.g. Kms with Iam)
            var allServiceDetails = new List <ServiceDetails>();

            foreach (var singlePackageFileDescs in byPackage)
            {
                var namespaces = singlePackageFileDescs.Select(x => x.CSharpNamespace()).Distinct().ToList();
                if (namespaces.Count > 1)
                {
                    throw new InvalidOperationException(
                              "All files in the same package must have the same C# namespace. " +
                              $"Found namespaces '{string.Join(", ", namespaces)}' in package '{singlePackageFileDescs.Key}'.");
                }
                var catalog = new ProtoCatalog(singlePackageFileDescs.Key, descriptors, singlePackageFileDescs, commonResourcesConfigs);
                foreach (var resultFile in GeneratePackage(namespaces[0], singlePackageFileDescs, catalog, clock, grpcServiceConfig, serviceConfig, allServiceDetails, transports))
                {
                    yield return(resultFile);
                }
            }

            // We assume that the first service we've generated corresponds to the service config (if we have one),
            // and is a service from the primary library we're generating. This is used for API validation and
            // gapic_metadata.json generation. This means it doesn't matter (for gapic_metadata.json)
            // if we're actually asked to generate more services than we really want. This currently
            // happens for services with IAM/location mix-ins, for example.
            var primaryLibraryProtoPackage = allServiceDetails.FirstOrDefault()?.ProtoPackage;
            var primaryLibraryServices     = allServiceDetails.Where(s => s.ProtoPackage == primaryLibraryProtoPackage).ToList();

            if (primaryLibraryServices.Any())
            {
                // Generate gapic_metadata.json, if there are any services.
                var gapicMetadataJsonContent = MetadataGenerator.GenerateGapicMetadataJson(primaryLibraryServices);
                yield return(new ResultFile("gapic_metadata.json", gapicMetadataJsonContent));
            }

            var unhandledApis = (serviceConfig?.Apis.Select(api => api.Name) ?? Enumerable.Empty <string>())
                                .Except(primaryLibraryServices.Select(s => s.ServiceFullName))
                                .Except(AllowedAdditionalServices)
                                .ToList();

            if (unhandledApis.Any())
            {
                throw new InvalidOperationException($"Unhandled APIs in service config: {string.Join(", ", unhandledApis)}");
            }
        }
コード例 #11
0
 internal FakeGrpcAdapter(ApiTransports transports = ApiTransports.Grpc) : base(transports)
 {
 }