Skip to content

Commit b40089c

Browse files
author
Guy Fankam
committed
Fix API revision issues and add API specification support
1 parent 267e4db commit b40089c

49 files changed

Lines changed: 5647 additions & 2100 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/.editorconfig

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Unix-style newlines with a newline ending every file
2+
[*]
3+
end_of_line = lf
4+
5+
[*.cs]
6+
7+
# CA1034: Nested types should not be visible
8+
dotnet_diagnostic.CA1034.severity = suggestion
9+
10+
# CA1062: Validate arguments of public methods
11+
dotnet_diagnostic.CA1062.severity = suggestion
12+
13+
# CA1707: Identifiers should not contain underscores
14+
dotnet_diagnostic.CA1707.severity = suggestion
15+
16+
# CA1710: Identifiers should have correct suffix
17+
dotnet_diagnostic.CA1710.severity = suggestion
18+
19+
# CA1715: Identifiers should have correct prefix
20+
dotnet_diagnostic.CA1715.severity = suggestion
21+
22+
# CA1724: Type names should not match namespaces
23+
dotnet_diagnostic.CA1724.severity = suggestion
24+
25+
# CA1848: Use the LoggerMessage delegates
26+
dotnet_diagnostic.CA1848.severity = suggestion
27+
28+
# CA1859: Use concrete types when possible for improved performance
29+
dotnet_diagnostic.CA1859.severity = suggestion
30+
31+
# CA2007: Consider calling ConfigureAwait on the awaited task
32+
dotnet_diagnostic.CA2007.severity = suggestion
33+
34+
# CA2225: Operator overloads have named alternates
35+
dotnet_diagnostic.CA2225.severity = suggestion
36+
37+
# CS8981: The type name only contains lower-cased ascii characters
38+
dotnet_diagnostic.CS8981.severity = suggestion
39+
40+
# IDE0008: Use explicit type
41+
dotnet_diagnostic.IDE0008.severity = suggestion
42+
csharp_style_var_elsewhere = true
43+
csharp_style_var_when_type_is_apparent = true
44+
45+
# IDE0010: Add missing cases
46+
dotnet_diagnostic.IDE0010.severity = suggestion
47+
48+
# IDE0021: Use block body for constructor
49+
csharp_style_expression_bodied_constructors = when_on_single_line
50+
51+
# IDE0022: Use block body for method
52+
csharp_style_expression_bodied_methods = when_on_single_line
53+
54+
# IDE0023: Use block body for conversion operator
55+
csharp_style_expression_bodied_operators = when_on_single_line
56+
57+
# IDE0058: Expression value is never used
58+
dotnet_diagnostic.IDE0058.severity = suggestion
59+
60+
# IDE0061: Use block body for local function
61+
csharp_style_expression_bodied_local_functions = when_on_single_line
62+
63+
# IDE0072: Add missing cases
64+
dotnet_diagnostic.IDE0072.severity = suggestion
65+
66+
# IDE0160: Convert to block scoped namespace
67+
csharp_style_namespace_declarations = file_scoped

src/aspire/aspire.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
<ImplicitUsings>enable</ImplicitUsings>
99
<Nullable>enable</Nullable>
1010
<IsAspireHost>true</IsAspireHost>
11-
<UserSecretsId>005123cc-339c-4ba3-a189-a918d08c6467</UserSecretsId>
11+
<UserSecretsId>6ce9dc18-ea0d-4d82-8f1a-e63877f35b88</UserSecretsId>
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.4.2" />
15+
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.5.1" />
1616
</ItemGroup>
1717

1818
<ItemGroup>

src/common/Api.cs

Lines changed: 117 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
using Azure.Core.Pipeline;
2+
using Flurl;
13
using System;
24
using System.Collections.Immutable;
5+
using System.IO;
36
using System.Linq;
7+
using System.Text.Json.Nodes;
48
using System.Text.Json.Serialization;
9+
using System.Threading;
10+
using System.Threading.Tasks;
511

612
namespace common;
713

@@ -48,6 +54,10 @@ public record ApiCreateOrUpdateProperties
4854
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
4955
public string? ApiRevisionDescription { get; init; }
5056

57+
[JsonPropertyName("apiType")]
58+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
59+
public string? ApiType { get; init; }
60+
5161
[JsonPropertyName("apiVersion")]
5262
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
5363
public string? ApiVersion { get; init; }
@@ -56,6 +66,10 @@ public record ApiCreateOrUpdateProperties
5666
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
5767
public string? ApiVersionDescription { get; init; }
5868

69+
[JsonPropertyName("apiVersionSet")]
70+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
71+
public ApiVersionSetContractDetails? ApiVersionSet { get; init; }
72+
5973
[JsonPropertyName("apiVersionSetId")]
6074
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
6175
public string? ApiVersionSetId { get; init; }
@@ -72,22 +86,6 @@ public record ApiCreateOrUpdateProperties
7286
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
7387
public string? Description { get; init; }
7488

75-
[JsonPropertyName("isCurrent")]
76-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
77-
public bool? IsCurrent { get; init; }
78-
79-
[JsonPropertyName("license")]
80-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
81-
public ApiLicenseInformation? License { get; init; }
82-
83-
[JsonPropertyName("apiType")]
84-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
85-
public string? ApiType { get; init; }
86-
87-
[JsonPropertyName("apiVersionSet")]
88-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
89-
public ApiVersionSetContractDetails? ApiVersionSet { get; init; }
90-
9189
[JsonPropertyName("displayName")]
9290
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
9391
public string? DisplayName { get; init; }
@@ -96,6 +94,14 @@ public record ApiCreateOrUpdateProperties
9694
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
9795
public string? Format { get; init; }
9896

97+
[JsonPropertyName("isCurrent")]
98+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
99+
public bool? IsCurrent { get; init; }
100+
101+
[JsonPropertyName("license")]
102+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
103+
public ApiLicenseInformation? License { get; init; }
104+
99105
[JsonPropertyName("protocols")]
100106
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
101107
public ImmutableArray<string>? Protocols { get; init; }
@@ -110,18 +116,6 @@ public record ApiCreateOrUpdateProperties
110116
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
111117
public string? SourceApiId { get; init; }
112118

113-
[JsonPropertyName("translateRequiredQueryParameters")]
114-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
115-
public string? TranslateRequiredQueryParameters { get; init; }
116-
117-
[JsonPropertyName("value")]
118-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
119-
public string? Value { get; init; }
120-
121-
[JsonPropertyName("wsdlSelector")]
122-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
123-
public WsdlSelectorContract? WsdlSelector { get; init; }
124-
125119
[JsonPropertyName("subscriptionKeyParameterNames")]
126120
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
127121
public SubscriptionKeyParameterNamesContract? SubscriptionKeyParameterNames { get; init; }
@@ -136,10 +130,22 @@ public record ApiCreateOrUpdateProperties
136130
public string? TermsOfServiceUrl { get; init; }
137131
#pragma warning restore CA1056 // URI-like properties should not be strings
138132

133+
[JsonPropertyName("translateRequiredQueryParameters")]
134+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
135+
public string? TranslateRequiredQueryParameters { get; init; }
136+
139137
[JsonPropertyName("type")]
140138
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
141139
public string? Type { get; init; }
142140

141+
[JsonPropertyName("value")]
142+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
143+
public string? Value { get; init; }
144+
145+
[JsonPropertyName("wsdlSelector")]
146+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
147+
public WsdlSelectorContract? WsdlSelector { get; init; }
148+
143149
public record AuthenticationSettingsContract
144150
{
145151
[JsonPropertyName("oAuth2")]
@@ -281,4 +287,87 @@ public static ResourceName Combine(ResourceName rootName, int revision) =>
281287
? throw new InvalidOperationException($"Revision must be positive.")
282288
: ResourceName.From($"{rootName}{separator}{revision}")
283289
.IfErrorThrow();
290+
}
291+
292+
public static partial class ResourceModule
293+
{
294+
private static async ValueTask PutApiInApim(ResourceName name, JsonObject dto, GetResourceDtoFromApim getApimDto, HttpPipeline pipeline, ServiceUri serviceUri, CancellationToken cancellationToken)
295+
{
296+
IResourceWithDto resource = ApiResource.Instance;
297+
ParentChain parentChain = ParentChain.Empty;
298+
299+
var uri = resource.GetUri(name, parentChain, serviceUri);
300+
var formattedDto = await formatDto();
301+
var result = await pipeline.PutJson(uri, formattedDto, cancellationToken);
302+
result.IfErrorThrow();
303+
304+
// Non-current revisions are not allowed to update certain properties.
305+
// Replace them with the existing revision properties if necessary.
306+
async ValueTask<JsonObject> formatDto()
307+
{
308+
// If this is the current revision, return the DTO as-is.
309+
var rootName = ApiRevisionModule.GetRootName(name);
310+
if (name == rootName)
311+
{
312+
return dto;
313+
}
314+
315+
// Otherwise, update the DTO with the appropriate current revision properties.
316+
var existingDto = await getApimDto(resource, rootName, parentChain, cancellationToken);
317+
318+
var result = from existingDtoObject in JsonNodeModule.To<ApiDto>(existingDto, resource.SerializerOptions)
319+
from newDtoObject in JsonNodeModule.To<ApiDto>(dto, resource.SerializerOptions)
320+
let formattedDtoObject = newDtoObject with
321+
{
322+
Properties = newDtoObject.Properties with
323+
{
324+
Type = existingDtoObject.Properties.Type,
325+
Description = existingDtoObject.Properties.Description,
326+
SubscriptionRequired = existingDtoObject.Properties.SubscriptionRequired,
327+
ApiVersion = existingDtoObject.Properties.ApiVersion,
328+
ApiRevisionDescription = existingDtoObject.Properties.ApiRevisionDescription,
329+
Path = existingDtoObject.Properties.Path,
330+
Protocols = existingDtoObject.Properties.Protocols
331+
}
332+
}
333+
from updatedDto in JsonObjectModule.From(formattedDtoObject, resource.SerializerOptions)
334+
select updatedDto;
335+
336+
return result.IfErrorThrow();
337+
}
338+
}
339+
340+
/// <summary>
341+
/// If the API type is not 'websocket' or 'graphql', remove the 'serviceUrl' property from the API information file DTO.
342+
/// </summary>
343+
private static JsonObject FormatInformationFileDto(this ApiResource resource, JsonObject dtoJson)
344+
{
345+
var serializerOptions = ((IResourceWithDto)resource).SerializerOptions;
346+
347+
var dto = JsonNodeModule.To<ApiDto>(dtoJson, serializerOptions)
348+
.IfErrorThrow();
349+
350+
dto = new[] { "websocket", "graphql" }.Contains(dto.Properties.Type, StringComparer.OrdinalIgnoreCase)
351+
? dto
352+
: dto with { Properties = dto.Properties with { ServiceUrl = null } };
353+
354+
return JsonObjectModule.From(dto, serializerOptions)
355+
.IfErrorThrow();
356+
}
357+
358+
private static Option<(ResourceName Name, ParentChain Ancestors)> ParseSpecificationFile(this ApiResource resource, FileInfo? file, ServiceDirectory serviceDirectory)
359+
{
360+
if (file is null)
361+
{
362+
return Option.None;
363+
}
364+
365+
var specificationFileNames = specifications.Select(GetSpecificationFileName);
366+
if (specificationFileNames.Contains(file.Name) is false)
367+
{
368+
return Option.None;
369+
}
370+
371+
return resource.ParseDirectory(file.Directory, serviceDirectory);
372+
}
284373
}

src/common/ApiRelease.cs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
using System;
1+
using Azure.Core.Pipeline;
2+
using System;
23
using System.Collections.Immutable;
4+
using System.Text.Json.Nodes;
35
using System.Text.Json.Serialization;
6+
using System.Threading;
7+
using System.Threading.Tasks;
48

59
namespace common;
610

@@ -22,7 +26,7 @@ private ApiReleaseResource() { }
2226

2327
public IResource Parent { get; } = ApiResource.Instance;
2428

25-
public ImmutableDictionary<IResource, string> MandatoryReferencedResourceDtoProperties { get; } =
29+
public ImmutableDictionary<IResource, string> OptionalReferencedResourceDtoProperties { get; } =
2630
ImmutableDictionary.Create<IResource, string>()
2731
.Add(ApiResource.Instance, nameof(ApiReleaseDto.Properties.ApiId));
2832

@@ -45,4 +49,46 @@ public record ApiReleaseContract
4549
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
4650
public string? Notes { get; init; }
4751
}
52+
}
53+
54+
public static partial class ResourceModule
55+
{
56+
private static async ValueTask PutApiReleaseInApim(ResourceName name,
57+
JsonObject dto,
58+
ParentChain ancestors,
59+
HttpPipeline pipeline,
60+
ServiceUri serviceUri,
61+
CancellationToken cancellationToken)
62+
{
63+
var resource = ApiReleaseResource.Instance;
64+
var resourceKey = new ResourceKey
65+
{
66+
Parents = ancestors,
67+
Name = name,
68+
Resource = resource
69+
};
70+
71+
var uri = resource.GetUri(name, ancestors, serviceUri);
72+
var formattedDto = formatDto(dto);
73+
var result = await pipeline.PutJson(uri, formattedDto, cancellationToken);
74+
result.IfErrorThrow();
75+
76+
JsonObject formatDto(JsonObject dtoJson)
77+
{
78+
var serializerOptions = ((IResourceWithDto)resource).SerializerOptions;
79+
80+
var result = from dto in JsonNodeModule.To<ApiReleaseDto>(dtoJson, serializerOptions)
81+
let formattedDto = dto with
82+
{
83+
Properties = dto.Properties with
84+
{
85+
ApiId = dto.Properties.ApiId ?? ancestors.ToResourceId()
86+
}
87+
}
88+
from formattedJson in JsonObjectModule.From(formattedDto, serializerOptions)
89+
select formattedJson;
90+
91+
return result.IfError(_ => dtoJson);
92+
}
93+
}
4894
}

0 commit comments

Comments
 (0)