Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Better support for generic types (#123)

### Fixed

- Allow enums to be used as query parameters in API clients (#133)

## [0.16.0] - 2024-12-17

### Added
Expand Down
59 changes: 59 additions & 0 deletions TypeContractor.Tests/TypeScript/ApiClientWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,59 @@ public void Writes_Get_With_Route()
.And.NotContain("url.searchParams.append(");
}

[Fact]
public void Handles_Get_With_Route_And_Query_Parameters()
{
// Arrange
var apiClient = new ApiClient("TestClient", "TestController", "test", null);
apiClient.AddEndpoint(new ApiClientEndpoint("getLatestId", "latest/{year}", EndpointMethod.GET, null, typeof(Guid), false,
[new EndpointParameter("year", typeof(int), null, false, false, true, false, false, false, false, false),
new EndpointParameter("reverse", typeof(bool?), null, false, false, false, true, false, false, false, true)], null));

// Act
var result = Sut.Write(apiClient, [], _converter, true, _templateFn, Casing.Pascal);

// Assert
var file = File.ReadAllText(result).Trim();
file.Should()
.NotBeEmpty()
.And.Contain("import { z } from 'zod';")
.And.Contain("export class TestClient {")
.And.Contain("public async getLatestId(year: number, reverse: boolean | undefined, cancellationToken: AbortSignal = null): Promise<string> {")
.And.Contain("const url = new URL(`test/latest/${year}`, window.location.origin);")
.And.Contain("if (!!reverse)")
.And.Contain("url.searchParams.append('reverse', reverse.toString());");
}

[Fact]
public void Handles_Enum_As_Query_Parameter()
{
// Arrange
var outputTypes = new List<OutputType>
{
_converter.Convert(typeof(ReportType))
};

var apiClient = new ApiClient("TestClient", "TestController", "test", null);
apiClient.AddEndpoint(new ApiClientEndpoint("getLatestId", "latest/{year}", EndpointMethod.GET, null, typeof(Guid), false,
[new EndpointParameter("year", typeof(int), null, false, false, true, false, false, false, false, false),
new EndpointParameter("reportType", typeof(ReportType), null, false, false, false, true, false, false, false, false)], null));

// Act
var result = Sut.Write(apiClient, outputTypes, _converter, true, _templateFn, Casing.Pascal);

// Assert
var file = File.ReadAllText(result).Trim();
file.Should()
.NotBeEmpty()
.And.Contain("import { z } from 'zod';")
.And.Contain("export class TestClient {")
.And.Contain("public async getLatestId(year: number, reportType: ReportType, cancellationToken: AbortSignal = null): Promise<string> {")
.And.Contain("const url = new URL(`test/latest/${year}`, window.location.origin);")
.And.NotContain("if (!!reportType)")
.And.Contain("url.searchParams.append('reportType', reportType.toString());");
}

[Fact]
public void Unpacks_Complex_Object_To_Query()
{
Expand Down Expand Up @@ -290,6 +343,12 @@ private class PaginatedRequest
public int PageSize { get; set; }
}

private enum ReportType
{
Summary = 0,
Detailed = 1,
}

private MetadataLoadContext BuildMetadataLoadContext()
{
// Get the array of runtime assemblies.
Expand Down
2 changes: 1 addition & 1 deletion TypeContractor/Helpers/CasingHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Globalization;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

Expand Down
6 changes: 6 additions & 0 deletions TypeContractor/TypeScript/ApiClientWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
[GeneratedRegex(@"([^\$])\{([A-Za-z0-9]+)\}")]
private static partial Regex RouteParameterRegex();

public string Write(ApiClient apiClient, IEnumerable<OutputType> allTypes, TypeScriptConverter converter, bool buildZodSchema, HandlebarsTemplate<object, ApiClientTemplateDto> template, Casing casing)

Check warning on line 26 in TypeContractor/TypeScript/ApiClientWriter.cs

View workflow job for this annotation

GitHub Actions / build

Argument type 'HandlebarsTemplate<object, ApiClientTemplateDto>' is not CLS-compliant

Check warning on line 26 in TypeContractor/TypeScript/ApiClientWriter.cs

View workflow job for this annotation

GitHub Actions / build

Argument type 'HandlebarsTemplate<object, ApiClientTemplateDto>' is not CLS-compliant

Check warning on line 26 in TypeContractor/TypeScript/ApiClientWriter.cs

View workflow job for this annotation

GitHub Actions / build

Argument type 'HandlebarsTemplate<object, ApiClientTemplateDto>' is not CLS-compliant

Check warning on line 26 in TypeContractor/TypeScript/ApiClientWriter.cs

View workflow job for this annotation

GitHub Actions / build

Argument type 'HandlebarsTemplate<object, ApiClientTemplateDto>' is not CLS-compliant
{
var _builder = new StringBuilder();
ArgumentNullException.ThrowIfNull(apiClient);
Expand Down Expand Up @@ -91,6 +91,12 @@
continue;
}

if (outputType.IsEnum)
{
queryParamsDto.Add(new QueryParameterTemplateDto(queryParam.Name, true, false, false, queryParam.IsOptional, null));
continue;
}

foreach (var property in outputType.Properties ?? [])
{
queryParamsDto.Add(new QueryParameterTemplateDto(queryParam.Name, false, property.IsNullable, false, queryParam.IsOptional, property.DestinationName));
Expand Down