From c9f656e8e4d49c09e43f4e6605a57c55b1dc06e7 Mon Sep 17 00:00:00 2001 From: Adam Metelski Date: Tue, 26 May 2026 09:23:42 -0700 Subject: [PATCH 1/2] fix: Preserve API DTO overrides during spec publish (#1) --- src/common/ApiSpecification.cs | 13 ++++++------- src/integration.tests/Apim.cs | 4 ++-- src/publisher.tests/Api.cs | 4 ++-- src/publisher.tests/WorkspaceApi.cs | 4 ++-- src/publisher/Api.cs | 14 +++++++------- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/common/ApiSpecification.cs b/src/common/ApiSpecification.cs index 70066559..8eb14458 100644 --- a/src/common/ApiSpecification.cs +++ b/src/common/ApiSpecification.cs @@ -20,7 +20,7 @@ namespace common; public delegate ValueTask> GetApiSpecificationFromApim(ResourceKey resourceKey, JsonObject dto, CancellationToken cancellationToken); public delegate ValueTask> GetApiSpecificationFromFile(ResourceKey resourceKey, ReadFile readFile, CancellationToken cancellationToken); public delegate ValueTask WriteApiSpecificationFile(ResourceKey resourceKey, ApiSpecification specification, BinaryData contents, CancellationToken cancellationToken); -public delegate ValueTask PutApiSpecificationInApim(ResourceKey resourceKey, ApiSpecification specification, BinaryData contents, CancellationToken cancellationToken); +public delegate ValueTask PutApiSpecificationInApim(ResourceKey resourceKey, JsonObject baseDto, ApiSpecification specification, BinaryData contents, CancellationToken cancellationToken); public abstract record ApiSpecification { @@ -519,7 +519,7 @@ private static WriteApiSpecificationFile ResolveWriteApiSpecificationFile(IServi { var serviceDirectory = provider.GetRequiredService(); - return async (resourceKey, specification, contents, cancellationToken) => + return async (resourceKey, baseDto, specification, contents, cancellationToken) => { var fileOption = GetSpecificationFile(resourceKey, specification, serviceDirectory); @@ -547,7 +547,7 @@ private static PutApiSpecificationInApim ResolvePutApiSpecificationInApim(IServi var resource = ApiResource.Instance; var ancestors = ParentChain.Empty; - return async (resourceKey, specification, contents, cancellationToken) => + return async (resourceKey, baseDto, specification, contents, cancellationToken) => { if (resourceKey.Resource is not ApiResource and not WorkspaceApiResource) { @@ -556,7 +556,7 @@ private static PutApiSpecificationInApim ResolvePutApiSpecificationInApim(IServi await (specification switch { - ApiSpecification.OpenApi openApiSpecification => putOpenApiSpecification(resourceKey, openApiSpecification, contents, cancellationToken), + ApiSpecification.OpenApi openApiSpecification => putOpenApiSpecification(resourceKey, baseDto, openApiSpecification, contents, cancellationToken), ApiSpecification.Wadl wadlSpecification => putWadlSpecification(resourceKey, wadlSpecification, contents, cancellationToken), ApiSpecification.Wsdl wsdlSpecification => putWsdlSpecification(resourceKey, wsdlSpecification, contents, cancellationToken), ApiSpecification.GraphQl graphQlSpecification => putGraphQlSpecification(resourceKey, graphQlSpecification, contents, cancellationToken), @@ -564,10 +564,9 @@ private static PutApiSpecificationInApim ResolvePutApiSpecificationInApim(IServi }); }; - async ValueTask putOpenApiSpecification(ResourceKey resourceKey, ApiSpecification.OpenApi specification, BinaryData contents, CancellationToken cancellationToken) + async ValueTask putOpenApiSpecification(ResourceKey resourceKey, JsonObject baseDto, ApiSpecification.OpenApi specification, BinaryData contents, CancellationToken cancellationToken) { - var resource = (IResourceWithDto)resourceKey.Resource; - var dto = await getDto(resource, resourceKey.Name, resourceKey.Parents, cancellationToken); + var dto = baseDto.DeepClone().AsObject(); dto = dto.MergeWith(new JsonObject { diff --git a/src/integration.tests/Apim.cs b/src/integration.tests/Apim.cs index c00d66b2..adb850d5 100644 --- a/src/integration.tests/Apim.cs +++ b/src/integration.tests/Apim.cs @@ -231,7 +231,7 @@ async ValueTask putResourceGroup(IGrouping group, Cancell await specificationOption.IterTask(async tuple => { var (specification, contents) = tuple; - await putApiSpecification(model.Key, specification, contents, cancellationToken); + await putApiSpecification(model.Key, model.ToDto(), specification, contents, cancellationToken); }); }, maxDegreeOfParallelism: Option.None, cancellationToken), cancellationToken), IResourceWithDto resourceWithDto => @@ -244,4 +244,4 @@ await specificationOption.IterTask(async tuple => }); } } -} \ No newline at end of file +} diff --git a/src/publisher.tests/Api.cs b/src/publisher.tests/Api.cs index dd37012e..9319f6e8 100644 --- a/src/publisher.tests/Api.cs +++ b/src/publisher.tests/Api.cs @@ -254,7 +254,7 @@ from specificationContents in Gen.Select(Generator.ApiSpecification, Generator.B await ValueTask.CompletedTask; return specificationContents; }, - PutApiSpecificationInApim = async (_, _, _, _) => + PutApiSpecificationInApim = async (_, _, _, _, _) => { await ValueTask.CompletedTask; } @@ -435,4 +435,4 @@ from dtoOption in Generator.JsonObject.OptionOf() } }; } -} \ No newline at end of file +} diff --git a/src/publisher.tests/WorkspaceApi.cs b/src/publisher.tests/WorkspaceApi.cs index 47b0fd50..4928ee2c 100644 --- a/src/publisher.tests/WorkspaceApi.cs +++ b/src/publisher.tests/WorkspaceApi.cs @@ -251,7 +251,7 @@ from specificationContents in Gen.Select(Generator.ApiSpecification, Generator.B await ValueTask.CompletedTask; return specificationContents; }, - PutApiSpecificationInApim = async (_, _, _, _) => + PutApiSpecificationInApim = async (_, _, _, _, _) => { await ValueTask.CompletedTask; } @@ -429,4 +429,4 @@ from dtoOption in Generator.JsonObject.OptionOf() } }; } -} \ No newline at end of file +} diff --git a/src/publisher/Api.cs b/src/publisher/Api.cs index efbe2911..3a125576 100644 --- a/src/publisher/Api.cs +++ b/src/publisher/Api.cs @@ -58,7 +58,7 @@ async ValueTask putApi(ResourceName name, JsonObject dto, CancellationToken canc await putResourceInApim(resource, name, dto, parents, cancellationToken); - await putSpecification(name, cancellationToken); + await putSpecification(name, dto, cancellationToken); } @@ -128,14 +128,14 @@ async ValueTask makeApiCurrent(ResourceName name, CancellationToken cancellation await deleteResourceFromApim(releaseResourceKey, ignoreNotFound: true, waitForCompletion: true, cancellationToken); } - async ValueTask putSpecification(ResourceName name, CancellationToken cancellationToken) + async ValueTask putSpecification(ResourceName name, JsonObject dto, CancellationToken cancellationToken) { var resourceKey = ResourceKey.From(resource, name, parents); var fileOperations = getCurrentFileOperations(); var specificationOption = await getSpecification(resourceKey, fileOperations.ReadFile, cancellationToken); - await specificationOption.IterTask(async specification => await putSpecificationInApim(resourceKey, specification.Specification, specification.Contents, cancellationToken)); + await specificationOption.IterTask(async specification => await putSpecificationInApim(resourceKey, dto, specification.Specification, specification.Contents, cancellationToken)); } } @@ -251,7 +251,7 @@ internal static PutWorkspaceApi ResolvePutWorkspaceApi(IServiceProvider provider await putResourceInApim(resource, name, dto, parents, cancellationToken); - await putSpecification(resourceKey, cancellationToken); + await putSpecification(resourceKey, dto, cancellationToken); }; async ValueTask setCurrentRevision(ResourceKey resourceKey, JsonObject dto, CancellationToken cancellationToken) @@ -294,14 +294,14 @@ async ValueTask getApimRevision(ResourceKey resourceKey, CancellationToken return GetWorkspaceApiRevisionFromDto(dtoJson); } - async ValueTask putSpecification(ResourceKey resourceKey, CancellationToken cancellationToken) + async ValueTask putSpecification(ResourceKey resourceKey, JsonObject dto, CancellationToken cancellationToken) { var fileOperations = getCurrentFileOperations(); var specificationOption = await getSpecification(resourceKey, fileOperations.ReadFile, cancellationToken); await specificationOption.IterTask(async specification => - await putSpecificationInApim(resourceKey, specification.Specification, specification.Contents, cancellationToken)); + await putSpecificationInApim(resourceKey, dto, specification.Specification, specification.Contents, cancellationToken)); } async ValueTask makeApiCurrent(ParentChain parents, ResourceName revisionedName, CancellationToken cancellationToken) @@ -402,4 +402,4 @@ async ValueTask> getCurrentRevision(ResourceKey resourceKey, Cancell })).WithCancellation(cancellationToken); } } -} \ No newline at end of file +} From 5b6c9cd64a00264f5b6f26909bece38f41e8a813 Mon Sep 17 00:00:00 2001 From: Adam Metelski Date: Tue, 26 May 2026 12:05:34 -0700 Subject: [PATCH 2/2] fix: WriteApiSpecificationFile delegate implementation signature (#2) --- src/common/ApiSpecification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/ApiSpecification.cs b/src/common/ApiSpecification.cs index 8eb14458..bd61e567 100644 --- a/src/common/ApiSpecification.cs +++ b/src/common/ApiSpecification.cs @@ -519,7 +519,7 @@ private static WriteApiSpecificationFile ResolveWriteApiSpecificationFile(IServi { var serviceDirectory = provider.GetRequiredService(); - return async (resourceKey, baseDto, specification, contents, cancellationToken) => + return async (resourceKey, specification, contents, cancellationToken) => { var fileOption = GetSpecificationFile(resourceKey, specification, serviceDirectory);