private static PutWorkspaceApi GetPutWorkspaceApi()

in tools/code/publisher/WorkspaceApi.cs [139:260]


    private static PutWorkspaceApi GetPutWorkspaceApi(IServiceProvider provider)
    {
        var findDto = provider.GetRequiredService<FindWorkspaceApiDto>();
        var findSpecificationContents = provider.GetRequiredService<FindWorkspaceApiSpecificationContents>();
        var correctRevisionNumber = provider.GetRequiredService<CorrectWorkspaceApimRevisionNumber>();
        var putInApim = provider.GetRequiredService<PutWorkspaceApiInApim>();
        var activitySource = provider.GetRequiredService<ActivitySource>();

        var taskDictionary = new ConcurrentDictionary<(ApiName, WorkspaceName), AsyncLazy<Unit>>();

        return putApi;

        async ValueTask putApi(ApiName name, WorkspaceName workspaceName, CancellationToken cancellationToken)
        {
            using var _ = activitySource.StartActivity(nameof(PutWorkspaceApi))
                                       ?.AddTag("workspace.name", workspaceName)
                                       ?.AddTag("workspace_api.name", name);

            await taskDictionary.GetOrAdd((name, workspaceName),
                                          (pair) => new AsyncLazy<Unit>(async cancellationToken =>
                                          {
                                              var (name, workspaceName) = pair;
                                              await putApiInner(name, workspaceName, cancellationToken);
                                              return Unit.Default;
                                          }))
                                .WithCancellation(cancellationToken);
        };

        async ValueTask putApiInner(ApiName name, WorkspaceName workspaceName, CancellationToken cancellationToken)
        {
            var informationFileDtoOption = await findDto(name, workspaceName, cancellationToken);
            await informationFileDtoOption.IterTask(async informationFileDto =>
            {
                await putCurrentRevision(name, informationFileDto, workspaceName, cancellationToken);
                var specificationContentsOption = await findSpecificationContents(name, workspaceName, cancellationToken);
                var dto = await tryGetDto(name, informationFileDto, specificationContentsOption, cancellationToken);
                var graphQlSpecificationContentsOption = specificationContentsOption.Bind(specificationContents =>
                {
                    var (specification, contents) = specificationContents;

                    return specification is ApiSpecification.GraphQl graphQl
                            ? (graphQl, contents)
                            : Option<(ApiSpecification.GraphQl, BinaryData)>.None;
                });
                await putInApim(name, dto, graphQlSpecificationContentsOption, workspaceName, cancellationToken);
            });
        }

        async ValueTask putCurrentRevision(ApiName name, WorkspaceApiDto dto, WorkspaceName workspaceName, CancellationToken cancellationToken)
        {
            if (ApiName.IsRevisioned(name))
            {
                var rootName = ApiName.GetRootName(name);
                await putApi(rootName, workspaceName, cancellationToken);
            }
            else
            {
                await correctRevisionNumber(name, dto, workspaceName, cancellationToken);
            }
        }

        async ValueTask<WorkspaceApiDto> tryGetDto(ApiName name,
                                                   WorkspaceApiDto informationFileDto,
                                                   Option<(ApiSpecification, BinaryData)> specificationContentsOption,
                                                   CancellationToken cancellationToken)
        {
            var dto = informationFileDto;

            await specificationContentsOption.IterTask(async specificationContents =>
            {
                var (specification, contents) = specificationContents;
                dto = await addSpecificationToDto(name, dto, specification, contents, cancellationToken);
            });

            return dto;
        }

        static async ValueTask<WorkspaceApiDto> addSpecificationToDto(ApiName name, WorkspaceApiDto dto, ApiSpecification specification, BinaryData contents, CancellationToken cancellationToken) =>
            dto with
            {
                Properties = dto.Properties with
                {
                    Format = specification switch
                    {
                        ApiSpecification.Wsdl => "wsdl",
                        ApiSpecification.Wadl => "wadl-xml",
                        ApiSpecification.OpenApi openApi => (openApi.Format, openApi.Version) switch
                        {
                            (common.OpenApiFormat.Json, OpenApiVersion.V2) => "swagger-json",
                            (common.OpenApiFormat.Json, OpenApiVersion.V3) => "openapi+json",
                            (common.OpenApiFormat.Yaml, OpenApiVersion.V2) => "openapi",
                            (common.OpenApiFormat.Yaml, OpenApiVersion.V3) => "openapi",
                            _ => throw new InvalidOperationException($"Unsupported OpenAPI format '{openApi.Format}' and version '{openApi.Version}'.")
                        },
                        _ => dto.Properties.Format
                    },
                    // APIM does not support OpenAPI V2 YAML. Convert to V3 YAML if needed.
                    Value = specification switch
                    {
                        ApiSpecification.GraphQl => null,
                        ApiSpecification.OpenApi { Format: common.OpenApiFormat.Yaml, Version: OpenApiVersion.V2 } =>
                            await convertStreamToOpenApiV3Yaml(contents, $"Could not convert specification for API {name} to OpenAPIV3.", cancellationToken),
                        _ => contents.ToString()
                    }
                }
            };

        static async ValueTask<string> convertStreamToOpenApiV3Yaml(BinaryData contents, string errorMessage, CancellationToken cancellationToken)
        {
            using var stream = contents.ToStream();
            var readResult = await new OpenApiStreamReader().ReadAsync(stream, cancellationToken);

            return readResult.OpenApiDiagnostic.Errors switch
            {
            [] => readResult.OpenApiDocument.Serialize(OpenApiSpecVersion.OpenApi3_0, Microsoft.OpenApi.OpenApiFormat.Yaml),
                var errors => throw openApiErrorsToException(errorMessage, errors)
            };
        }

        static OpenApiException openApiErrorsToException(string message, IEnumerable<OpenApiError> errors) =>
            new($"{message}. Errors are: {Environment.NewLine}{string.Join(Environment.NewLine, errors)}");
    }