tools/code/extractor/Api.cs (204 lines of code) (raw):
using Azure.Core.Pipeline;
using common;
using Flurl;
using LanguageExt;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace extractor;
public delegate ValueTask ExtractApis(CancellationToken cancellationToken);
public delegate IAsyncEnumerable<(ApiName Name, ApiDto Dto, Option<(ApiSpecification Specification, BinaryData Contents)> SpecificationOption)> ListApis(CancellationToken cancellationToken);
public delegate ValueTask WriteApiArtifacts(ApiName name, ApiDto dto, Option<(ApiSpecification Specification, BinaryData Contents)> specificationOption, CancellationToken cancellationToken);
public delegate ValueTask WriteApiInformationFile(ApiName name, ApiDto dto, CancellationToken cancellationToken);
public delegate ValueTask WriteApiSpecificationFile(ApiName name, ApiSpecification specification, BinaryData contents, CancellationToken cancellationToken);
internal static class ApiModule
{
public static void ConfigureExtractApis(IHostApplicationBuilder builder)
{
ConfigureListApis(builder);
ConfigureWriteApiArtifacts(builder);
ApiPolicyModule.ConfigureExtractApiPolicies(builder);
ApiTagModule.ConfigureExtractApiTags(builder);
ApiDiagnosticModule.ConfigureExtractApiDiagnostics(builder);
ApiOperationModule.ConfigureExtractApiOperations(builder);
builder.Services.TryAddSingleton(GetExtractApis);
}
private static ExtractApis GetExtractApis(IServiceProvider provider)
{
var list = provider.GetRequiredService<ListApis>();
var writeArtifacts = provider.GetRequiredService<WriteApiArtifacts>();
var extractApiPolicies = provider.GetRequiredService<ExtractApiPolicies>();
var extractApiTags = provider.GetRequiredService<ExtractApiTags>();
var extractApiDiagnostics = provider.GetRequiredService<ExtractApiDiagnostics>();
var extractApiOperations = provider.GetRequiredService<ExtractApiOperations>();
var activitySource = provider.GetRequiredService<ActivitySource>();
var logger = provider.GetRequiredService<ILogger>();
return async cancellationToken =>
{
using var _ = activitySource.StartActivity(nameof(ExtractApis));
logger.LogInformation("Extracting APIs...");
await list(cancellationToken)
// Group APIs by version set (https://github.com/Azure/apiops/issues/316).
// We'll process each group in parallel, but each API within a group sequentially.
.GroupBy(api => api.Dto.Properties.ApiVersionSetId ?? Guid.NewGuid().ToString())
.IterParallel(async group => await group.Iter(async api => await extractApi(api.Name, api.Dto, api.SpecificationOption, cancellationToken),
cancellationToken),
cancellationToken);
};
async ValueTask extractApi(ApiName name, ApiDto dto, Option<(ApiSpecification Specification, BinaryData Contents)> specificationOption, CancellationToken cancellationToken)
{
await writeArtifacts(name, dto, specificationOption, cancellationToken);
await extractApiPolicies(name, cancellationToken);
await extractApiTags(name, cancellationToken);
await extractApiDiagnostics(name, cancellationToken);
await extractApiOperations(name, cancellationToken);
}
}
private static void ConfigureListApis(IHostApplicationBuilder builder)
{
ConfigurationModule.ConfigureFindConfigurationNamesFactory(builder);
ApiSpecificationModule.ConfigureDefaultApiSpecification(builder);
AzureModule.ConfigureManagementServiceUri(builder);
AzureModule.ConfigureHttpPipeline(builder);
builder.Services.TryAddSingleton(GetListApis);
}
private static ListApis GetListApis(IServiceProvider provider)
{
var findConfigurationNamesFactory = provider.GetRequiredService<FindConfigurationNamesFactory>();
var defaultApiSpecification = provider.GetRequiredService<DefaultApiSpecification>();
var serviceUri = provider.GetRequiredService<ManagementServiceUri>();
var pipeline = provider.GetRequiredService<HttpPipeline>();
var findConfigurationApis = findConfigurationNamesFactory.Create<ApiName>();
var findConfigurationVersionSets = findConfigurationNamesFactory.Create<VersionSetName>();
return cancellationToken =>
list(cancellationToken)
.Where(api => shouldExtractApiDto(api.Dto))
.SelectAwait(async api =>
{
var (name, dto) = api;
var specificationContentsOption = await tryGetSpecificationContents(name, dto, cancellationToken);
return (name, dto, specificationContentsOption);
});
IAsyncEnumerable<(ApiName Name, ApiDto Dto)> list(CancellationToken cancellationToken) =>
findConfigurationApis()
.Map(names => listFromSet(names, cancellationToken))
.IfNone(() => listAll(cancellationToken));
IAsyncEnumerable<(ApiName, ApiDto)> listFromSet(IEnumerable<ApiName> names, CancellationToken cancellationToken) =>
names.ToAsyncEnumerable()
// Ensure API exists
.WhereAwait(async name =>
{
var uri = ApiUri.From(name, serviceUri);
var dtoOption = await uri.TryGetDto(pipeline, cancellationToken);
return dtoOption.IsSome;
})
// Get all API revisions
.SelectMany(name => listAllRevisions(name, cancellationToken));
IAsyncEnumerable<(ApiName, ApiDto)> listAllRevisions(ApiName name, CancellationToken cancellationToken)
{
var rootName = ApiName.GetRootName(name);
var rootNameUri = ApiUri.From(name, serviceUri);
var revisionsUri = rootNameUri.ToUri().AppendPathSegment("revisions").ToUri();
return pipeline.ListJsonObjects(revisionsUri, cancellationToken)
// Get name for each revision. If the revision is current, use the root name.
.Select(jsonObject =>
{
var revisionNumberInt = jsonObject.GetIntProperty("apiRevision");
var revisionNumber = ApiRevisionNumber.From(revisionNumberInt);
var isCurrent = jsonObject.GetBoolProperty("isCurrent");
return isCurrent ? name : ApiName.GetRevisionedName(rootName, revisionNumber);
})
// Get DTO for each revision
.SelectAwait(async name =>
{
var uri = ApiUri.From(name, serviceUri);
var dto = await uri.GetDto(pipeline, cancellationToken);
return (name, dto);
});
}
IAsyncEnumerable<(ApiName, ApiDto)> listAll(CancellationToken cancellationToken) =>
ApisUri.From(serviceUri)
.List(pipeline, cancellationToken);
bool shouldExtractApiDto(ApiDto dto) =>
// Don't extract if its version set should not be extracted
common.ApiModule.TryGetVersionSetName(dto)
.Map(shouldExtractVersionSet)
.IfNone(true);
bool shouldExtractVersionSet(VersionSetName name) =>
findConfigurationVersionSets()
.Map(names => names.Contains(name))
.IfNone(true);
async ValueTask<Option<(ApiSpecification, BinaryData)>> tryGetSpecificationContents(ApiName name, ApiDto dto, CancellationToken cancellationToken)
{
var specificationOption = tryGetSpecification(dto);
return await specificationOption.BindTask(async specification =>
{
var uri = ApiUri.From(name, serviceUri);
var contentsOption = await uri.TryGetSpecificationContents(specification, pipeline, cancellationToken);
return from contents in contentsOption
select (specification, contents);
});
}
Option<ApiSpecification> tryGetSpecification(ApiDto dto) =>
(dto.Properties.Type ?? dto.Properties.ApiType) switch
{
"graphql" => new ApiSpecification.GraphQl(),
"soap" => new ApiSpecification.Wsdl(),
"http" => defaultApiSpecification.Value,
null => defaultApiSpecification.Value,
_ => Option<ApiSpecification>.None
};
}
private static void ConfigureWriteApiArtifacts(IHostApplicationBuilder builder)
{
ConfigureWriteApiInformationFile(builder);
ConfigureWriteApiSpecificationFile(builder);
builder.Services.TryAddSingleton(GetWriteApiArtifacts);
}
private static WriteApiArtifacts GetWriteApiArtifacts(IServiceProvider provider)
{
var writeInformationFile = provider.GetRequiredService<WriteApiInformationFile>();
var writeSpecificationFile = provider.GetRequiredService<WriteApiSpecificationFile>();
return async (name, dto, specificationContentsOption, cancellationToken) =>
{
await writeInformationFile(name, dto, cancellationToken);
await specificationContentsOption.IterTask(async x =>
{
var (specification, contents) = x;
await writeSpecificationFile(name, specification, contents, cancellationToken);
});
};
}
private static void ConfigureWriteApiInformationFile(IHostApplicationBuilder builder)
{
AzureModule.ConfigureManagementServiceDirectory(builder);
builder.Services.TryAddSingleton(GetWriteApiInformationFile);
}
private static WriteApiInformationFile GetWriteApiInformationFile(IServiceProvider provider)
{
var serviceDirectory = provider.GetRequiredService<ManagementServiceDirectory>();
var logger = provider.GetRequiredService<ILogger>();
return async (name, dto, cancellationToken) =>
{
var informationFile = ApiInformationFile.From(name, serviceDirectory);
logger.LogInformation("Writing API information file {ApiInformationFile}...", informationFile);
await informationFile.WriteDto(dto, cancellationToken);
};
}
private static void ConfigureWriteApiSpecificationFile(IHostApplicationBuilder builder)
{
AzureModule.ConfigureManagementServiceDirectory(builder);
builder.Services.TryAddSingleton(GetWriteApiSpecificationFile);
}
private static WriteApiSpecificationFile GetWriteApiSpecificationFile(IServiceProvider provider)
{
var serviceDirectory = provider.GetRequiredService<ManagementServiceDirectory>();
var logger = provider.GetRequiredService<ILogger>();
return async (name, specification, contents, cancellationToken) =>
{
var specificationFile = ApiSpecificationFile.From(specification, name, serviceDirectory);
logger.LogInformation("Writing API specification file {ApiSpecificationFile}...", specificationFile);
await specificationFile.WriteSpecification(contents, cancellationToken);
};
}
}