From 5726db2532f9b1a88e8bd2b5866041f768bf85ad Mon Sep 17 00:00:00 2001
From: Pavel Vostretsov
Date: Wed, 10 Jan 2024 20:06:53 +0500
Subject: [PATCH] use string templates for urls
---
.../Controllers/WeatherForecastController.cs | 19 +-
.../output/api/NotesApi.ts | 15 +-
.../output/api/UserApi.ts | 33 +--
.../output/api/WeatherForecastApi.ts | 42 ++--
.../output/apiBase/ApiBase.ts | 37 +--
...ControllerTypeBuildingContext.Expected.txt | 28 +--
.../ApiControllerTypeBuildingContext.txt | 29 ++-
.../OptionsTests.cs | 7 +
.../TypeScriptTemplateStringLiteral.cs | 7 +-
.../ApiControllerTypeBuildingContext.cs | 192 +++++++--------
.../ApiControllerTypeBuildingContextBase.cs | 225 ------------------
.../ApiController/BaseApiMethod.cs | 13 -
.../ApiController/DefaultApiCustomization.cs | 141 +++++++++++
.../ApiController/IApiCustomization.cs | 27 +++
.../ApiController/KnownTypeNames.cs | 1 +
.../ApiController/RouteTemplate.cs | 77 ------
.../ApiController/RouteTemplateHelper.cs | 21 +-
.../ApiController/UrlOnlyAttribute.cs | 8 -
18 files changed, 384 insertions(+), 538 deletions(-)
delete mode 100644 TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContextBase.cs
delete mode 100644 TypeScript.ContractGenerator/TypeBuilders/ApiController/BaseApiMethod.cs
create mode 100644 TypeScript.ContractGenerator/TypeBuilders/ApiController/DefaultApiCustomization.cs
create mode 100644 TypeScript.ContractGenerator/TypeBuilders/ApiController/IApiCustomization.cs
delete mode 100644 TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplate.cs
delete mode 100644 TypeScript.ContractGenerator/TypeBuilders/ApiController/UrlOnlyAttribute.cs
diff --git a/AspNetCoreExample.Api/Controllers/WeatherForecastController.cs b/AspNetCoreExample.Api/Controllers/WeatherForecastController.cs
index 5cf3a1b..4561b96 100644
--- a/AspNetCoreExample.Api/Controllers/WeatherForecastController.cs
+++ b/AspNetCoreExample.Api/Controllers/WeatherForecastController.cs
@@ -4,8 +4,6 @@
using Microsoft.AspNetCore.Mvc;
-using SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController;
-
namespace AspNetCoreExample.Api.Controllers;
[ApiController]
@@ -36,11 +34,11 @@ public void Update(string city, [FromBody] WeatherForecast forecast, Cancellatio
}
[HttpPost("~/[action]")]
- public void Reset(int seed)
+ public IActionResult Reset(int seed)
{
+ return Ok();
}
- [UrlOnly]
[HttpGet("{city}")]
public ActionResult Download(string city)
{
@@ -53,6 +51,19 @@ public ActionResult Download(string city)
return File(JsonSerializer.SerializeToUtf8Bytes(forecast), "application/json");
}
+ [HttpGet("{street}/view")]
+ public async Task GetStreetView(string street, bool useGoogleImages)
+ {
+ await Task.Delay(100);
+ return File(Array.Empty(), "image/jpeg");
+ }
+
+ [HttpGet("none")]
+ public Task> NewGuid()
+ {
+ return Task.FromResult((ActionResult)Ok(Guid.NewGuid()));
+ }
+
private static readonly string[] summaries =
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
diff --git a/AspNetCoreExample.Generator/output/api/NotesApi.ts b/AspNetCoreExample.Generator/output/api/NotesApi.ts
index ec3b315..db65a53 100644
--- a/AspNetCoreExample.Generator/output/api/NotesApi.ts
+++ b/AspNetCoreExample.Generator/output/api/NotesApi.ts
@@ -3,20 +3,15 @@
import { Guid } from './../DataTypes/Guid';
import { BlogEntry } from './../DataTypes/BlogEntry';
import { ApiBase } from './../ApiBase/ApiBase';
+import { url } from './../ApiBase/ApiBase';
export class NotesApi extends ApiBase implements INotesApi {
- async addEntry(userId: Guid, entry: BlogEntry): Promise {
- return this.makePostRequest(`/v1/user/${userId}/blog`, {
-
- }, {
- ...entry,
- });
+ addEntry(userId: Guid, entry: BlogEntry): Promise {
+ return this.makePostRequest(url`/v1/user/${userId}/blog`, entry);
}
- async addEntries(userId: Guid, entries: BlogEntry[]): Promise {
- return this.makePostRequest(`/v1/user/${userId}/blog/batch`, {
-
- }, entries);
+ addEntries(userId: Guid, entries: BlogEntry[]): Promise {
+ return this.makePostRequest(url`/v1/user/${userId}/blog/batch`, entries);
}
};
diff --git a/AspNetCoreExample.Generator/output/api/UserApi.ts b/AspNetCoreExample.Generator/output/api/UserApi.ts
index bab0e5c..b62c2b2 100644
--- a/AspNetCoreExample.Generator/output/api/UserApi.ts
+++ b/AspNetCoreExample.Generator/output/api/UserApi.ts
@@ -3,38 +3,23 @@
import { User } from './../DataTypes/User';
import { Guid } from './../DataTypes/Guid';
import { ApiBase } from './../ApiBase/ApiBase';
+import { url } from './../ApiBase/ApiBase';
export class UserApi extends ApiBase implements IUserApi {
- async createUser(user: User): Promise {
- return this.makePostRequest(`/v1/users`, {
-
- }, {
- ...user,
- });
+ createUser(user: User): Promise {
+ return this.makePostRequest(url`/v1/users`, user);
}
- async deleteUser(userId: Guid): Promise {
- return this.makeDeleteRequest(`/v1/users/${userId}`, {
-
- }, {
-
- });
+ deleteUser(userId: Guid): Promise {
+ return this.makeDeleteRequest(url`/v1/users/${userId}`);
}
- async getUser(userId: Guid): Promise {
- return this.makeGetRequest(`/v1/users/${userId}`, {
-
- }, {
-
- });
+ getUser(userId: Guid): Promise {
+ return this.makeGetRequest(url`/v1/users/${userId}`);
}
- async searchUsers(name: string): Promise {
- return this.makeGetRequest(`/v1/users`, {
- ['name']: name,
- }, {
-
- });
+ searchUsers(name: string): Promise {
+ return this.makeGetRequest(url`/v1/users?name=${name}`);
}
};
diff --git a/AspNetCoreExample.Generator/output/api/WeatherForecastApi.ts b/AspNetCoreExample.Generator/output/api/WeatherForecastApi.ts
index 52ca0c4..f7caa54 100644
--- a/AspNetCoreExample.Generator/output/api/WeatherForecastApi.ts
+++ b/AspNetCoreExample.Generator/output/api/WeatherForecastApi.ts
@@ -1,35 +1,33 @@
// tslint:disable
// TypeScriptContractGenerator's generated content
import { WeatherForecast } from './../DataTypes/WeatherForecast';
+import { Guid } from './../DataTypes/Guid';
import { ApiBase } from './../ApiBase/ApiBase';
+import { url } from './../ApiBase/ApiBase';
export class WeatherForecastApi extends ApiBase implements IWeatherForecastApi {
- async get(): Promise {
- return this.makeGetRequest(`/WeatherForecast`, {
-
- }, {
-
- });
+ get(): Promise {
+ return this.makeGetRequest(url`/WeatherForecast`);
}
- async update(city: string, forecast: WeatherForecast): Promise {
- return this.makePostRequest(`/WeatherForecast/Update/${city}`, {
-
- }, {
- ...forecast,
- });
+ update(city: string, forecast: WeatherForecast): Promise {
+ return this.makePostRequest(url`/WeatherForecast/Update/${city}`, forecast);
}
- async reset(seed: number): Promise {
- return this.makePostRequest(`/Reset`, {
- ['seed']: seed,
- }, {
-
- });
+ reset(seed: number): Promise {
+ return this.makePostRequest(url`/Reset?seed=${seed}`);
}
- getDownloadUrl(city: string): string {
- return `/WeatherForecast/${city}`;
+ urlForDownload(city: string): string {
+ return url`/WeatherForecast/${city}`;
+ }
+
+ urlForGetStreetView(street: string, useGoogleImages: boolean): string {
+ return url`/WeatherForecast/${street}/view?useGoogleImages=${useGoogleImages}`;
+ }
+
+ newGuid(): Promise {
+ return this.makeGetRequest(url`/WeatherForecast/none`);
}
};
@@ -37,5 +35,7 @@ export interface IWeatherForecastApi {
get(): Promise;
update(city: string, forecast: WeatherForecast): Promise;
reset(seed: number): Promise;
- getDownloadUrl(city: string): string;
+ urlForDownload(city: string): string;
+ urlForGetStreetView(street: string, useGoogleImages: boolean): string;
+ newGuid(): Promise;
}
diff --git a/AspNetCoreExample.Generator/output/apiBase/ApiBase.ts b/AspNetCoreExample.Generator/output/apiBase/ApiBase.ts
index 8e0beb4..74a9f03 100644
--- a/AspNetCoreExample.Generator/output/apiBase/ApiBase.ts
+++ b/AspNetCoreExample.Generator/output/apiBase/ApiBase.ts
@@ -1,21 +1,19 @@
/* eslint-disable */
-interface IParamsMap {
- [key: string]: null | undefined | number | string | any[] | boolean;
-}
+export const url = String.raw;
export class ApiBase {
- public async makeGetRequest(url: string, queryParams: IParamsMap, body: any): Promise {
- const response = await fetch(this.getUrl(url, queryParams), {
+ public async makeGetRequest(url: string, body?: any): Promise {
+ const response = await fetch(url, {
method: "GET",
});
return await response.json();
}
- public async makePostRequest(url: string, queryParams: IParamsMap, body: any): Promise {
- const response = await fetch(this.getUrl(url, queryParams), {
+ public async makePostRequest(url: string, body?: any): Promise {
+ const response = await fetch(url, {
method: "POST",
- body: JSON.stringify(body),
+ body: body && JSON.stringify(body),
});
const textResult = await response.text();
if (textResult !== "") {
@@ -23,10 +21,10 @@ export class ApiBase {
}
}
- public async makePutRequest(url: string, queryParams: IParamsMap, body: any): Promise {
- const response = await fetch(this.getUrl(url, queryParams), {
+ public async makePutRequest(url: string, body?: any): Promise {
+ const response = await fetch(url, {
method: "PUT",
- body: JSON.stringify(body),
+ body: body && JSON.stringify(body),
});
const textResult = await response.text();
if (textResult !== "") {
@@ -34,25 +32,14 @@ export class ApiBase {
}
}
- public async makeDeleteRequest(url: string, queryParams: IParamsMap, body: any): Promise {
- const response = await fetch(this.getUrl(url, queryParams), {
+ public async makeDeleteRequest(url: string, body?: any): Promise {
+ const response = await fetch(url, {
method: "DELETE",
- body: JSON.stringify(body),
+ body: body && JSON.stringify(body),
});
const textResult = await response.text();
if (textResult !== "") {
return JSON.parse(textResult);
}
}
-
- public getUrl(url: string, params?: IParamsMap): string {
- return url + this.createQueryString(params);
- }
-
- public createQueryString(params: any): string {
- if (params == null) {
- return "";
- }
- return `${new URLSearchParams(params)}`;
- }
}
diff --git a/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.Expected.txt b/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.Expected.txt
index c7a5205..93cca8f 100644
--- a/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.Expected.txt
+++ b/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.Expected.txt
@@ -6,11 +6,11 @@ using SkbKontur.TypeScript.ContractGenerator;
using SkbKontur.TypeScript.ContractGenerator.Abstractions;
using SkbKontur.TypeScript.ContractGenerator.Extensions;
using SkbKontur.TypeScript.ContractGenerator.Internals;
-using SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController;
+using SkbKontur.TypeScript.ContractGenerator.TypeBuilders;
namespace AspNetCoreExample.Generator
{
- public class ApiControllerTypeBuildingContext : ApiControllerTypeBuildingContextBase
+ public class ApiControllerTypeBuildingContext : TypeBuildingContext
{
public ApiControllerTypeBuildingContext(TypeScriptUnit unit, ITypeInfo type)
: base(unit, type)
@@ -29,7 +29,7 @@ namespace AspNetCoreExample.Generator
return SkbKontur.TypeScript.ContractGenerator.Roslyn.TypeInfoRewriter.Types[6].IsAssignableFrom(type);
}
- protected override TypeLocation GetApiBase(ITypeInfo controllerType)
+ protected TypeLocation GetApiBase(ITypeInfo controllerType)
{
var apiBaseName = GetApiBaseName(controllerType);
return new TypeLocation
@@ -46,7 +46,7 @@ namespace AspNetCoreExample.Generator
return "ApiBase";
}
- protected override ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
+ protected ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
{
if (typeInfo.IsGenericType)
{
@@ -62,24 +62,24 @@ namespace AspNetCoreExample.Generator
return typeInfo;
}
- protected override BaseApiMethod ResolveBaseApiMethod(IMethodInfo methodInfo)
+ protected string ResolveBaseApiMethod(IMethodInfo methodInfo)
{
if (methodInfo.GetAttributes(SkbKontur.TypeScript.ContractGenerator.Roslyn.TypeInfoRewriter.Types[13]).Any())
- return BaseApiMethod.Get;
+ return "Get";
if (methodInfo.GetAttributes(SkbKontur.TypeScript.ContractGenerator.Roslyn.TypeInfoRewriter.Types[14]).Any())
- return BaseApiMethod.Post;
+ return "Post";
if (methodInfo.GetAttributes(SkbKontur.TypeScript.ContractGenerator.Roslyn.TypeInfoRewriter.Types[15]).Any())
- return BaseApiMethod.Put;
+ return "Put";
if (methodInfo.GetAttributes(SkbKontur.TypeScript.ContractGenerator.Roslyn.TypeInfoRewriter.Types[16]).Any())
- return BaseApiMethod.Delete;
+ return "Delete";
throw new NotSupportedException($"Unresolved http verb for method {methodInfo.Name} at controller {methodInfo.DeclaringType?.Name}");
}
- protected override string BuildRoute(ITypeInfo controllerType, IMethodInfo methodInfo)
+ protected string BuildRoute(ITypeInfo controllerType, IMethodInfo methodInfo)
{
var routeTemplate = methodInfo.GetAttributes(false)
.Select(x => x.AttributeData.TryGetValue("Template", out var value) ? (string)value : null)
@@ -87,24 +87,24 @@ namespace AspNetCoreExample.Generator
return AppendRoutePrefix(routeTemplate, controllerType);
}
- protected override bool PassParameterToCall(IParameterInfo parameterInfo, ITypeInfo controllerType)
+ protected bool PassParameterToCall(IParameterInfo parameterInfo, ITypeInfo controllerType)
{
if (IsUserScopedApi(controllerType) && parameterInfo.Name == "userId")
return false;
return true;
}
- protected override IParameterInfo[] GetQueryParameters(IParameterInfo[] parameters, ITypeInfo controllerType)
+ protected IParameterInfo[] GetQueryParameters(IParameterInfo[] parameters, ITypeInfo controllerType)
{
return parameters.Where(x => PassParameterToCall(x, controllerType) && !x.GetAttributes(SkbKontur.TypeScript.ContractGenerator.Roslyn.TypeInfoRewriter.Types[17]).Any()).ToArray();
}
- protected override IParameterInfo GetBody(IParameterInfo[] parameters, ITypeInfo controllerType)
+ protected IParameterInfo GetBody(IParameterInfo[] parameters, ITypeInfo controllerType)
{
return parameters.SingleOrDefault(x => PassParameterToCall(x, controllerType) && x.GetAttributes(SkbKontur.TypeScript.ContractGenerator.Roslyn.TypeInfoRewriter.Types[18]).Any());
}
- protected override IMethodInfo[] GetMethodsToImplement(ITypeInfo controllerType)
+ protected IMethodInfo[] GetMethodsToImplement(ITypeInfo controllerType)
{
return controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Where(m => !((MethodWrapper)m).Method.IsSpecialName)
diff --git a/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.txt b/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.txt
index 64ccedf..8bf6581 100644
--- a/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.txt
+++ b/TypeScript.ContractGenerator.Tests/Files/ApiControllerTypeBuildingContext.txt
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Reflection;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
@@ -9,7 +8,7 @@ using SkbKontur.TypeScript.ContractGenerator;
using SkbKontur.TypeScript.ContractGenerator.Abstractions;
using SkbKontur.TypeScript.ContractGenerator.Extensions;
using SkbKontur.TypeScript.ContractGenerator.Internals;
-using SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController;
+using SkbKontur.TypeScript.ContractGenerator.TypeBuilders;
using static SkbKontur.TypeScript.ContractGenerator.Internals.TypeInfo;
@@ -18,7 +17,7 @@ using AnotherTypeInfo = SkbKontur.TypeScript.ContractGenerator.Internals.TypeInf
namespace AspNetCoreExample.Generator
{
- public class ApiControllerTypeBuildingContext : ApiControllerTypeBuildingContextBase
+ public class ApiControllerTypeBuildingContext : TypeBuildingContext
{
public ApiControllerTypeBuildingContext(TypeScriptUnit unit, ITypeInfo type)
: base(unit, type)
@@ -37,7 +36,7 @@ namespace AspNetCoreExample.Generator
return TypeInfo.From().IsAssignableFrom(type);
}
- protected override TypeLocation GetApiBase(ITypeInfo controllerType)
+ protected TypeLocation GetApiBase(ITypeInfo controllerType)
{
var apiBaseName = GetApiBaseName(controllerType);
return new TypeLocation
@@ -54,7 +53,7 @@ namespace AspNetCoreExample.Generator
return "ApiBase";
}
- protected override ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
+ protected ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
{
if (typeInfo.IsGenericType)
{
@@ -70,24 +69,24 @@ namespace AspNetCoreExample.Generator
return typeInfo;
}
- protected override BaseApiMethod ResolveBaseApiMethod(IMethodInfo methodInfo)
+ protected string ResolveBaseApiMethod(IMethodInfo methodInfo)
{
if (methodInfo.GetAttributes(TypeInfo.From()).Any())
- return BaseApiMethod.Get;
+ return "Get";
if (methodInfo.GetAttributes(TypeInfo.From()).Any())
- return BaseApiMethod.Post;
+ return "Post";
if (methodInfo.GetAttributes(TypeInfo.From()).Any())
- return BaseApiMethod.Put;
+ return "Put";
if (methodInfo.GetAttributes(TypeInfo.From()).Any())
- return BaseApiMethod.Delete;
+ return "Delete";
throw new NotSupportedException($"Unresolved http verb for method {methodInfo.Name} at controller {methodInfo.DeclaringType?.Name}");
}
- protected override string BuildRoute(ITypeInfo controllerType, IMethodInfo methodInfo)
+ protected string BuildRoute(ITypeInfo controllerType, IMethodInfo methodInfo)
{
var routeTemplate = methodInfo.GetAttributes(false)
.Select(x => x.AttributeData.TryGetValue("Template", out var value) ? (string)value : null)
@@ -95,24 +94,24 @@ namespace AspNetCoreExample.Generator
return AppendRoutePrefix(routeTemplate, controllerType);
}
- protected override bool PassParameterToCall(IParameterInfo parameterInfo, ITypeInfo controllerType)
+ protected bool PassParameterToCall(IParameterInfo parameterInfo, ITypeInfo controllerType)
{
if (IsUserScopedApi(controllerType) && parameterInfo.Name == "userId")
return false;
return true;
}
- protected override IParameterInfo[] GetQueryParameters(IParameterInfo[] parameters, ITypeInfo controllerType)
+ protected IParameterInfo[] GetQueryParameters(IParameterInfo[] parameters, ITypeInfo controllerType)
{
return parameters.Where(x => PassParameterToCall(x, controllerType) && !x.GetAttributes(TypeInfo.From()).Any()).ToArray();
}
- protected override IParameterInfo GetBody(IParameterInfo[] parameters, ITypeInfo controllerType)
+ protected IParameterInfo GetBody(IParameterInfo[] parameters, ITypeInfo controllerType)
{
return parameters.SingleOrDefault(x => PassParameterToCall(x, controllerType) && x.GetAttributes(TypeInfo.From()).Any());
}
- protected override IMethodInfo[] GetMethodsToImplement(ITypeInfo controllerType)
+ protected IMethodInfo[] GetMethodsToImplement(ITypeInfo controllerType)
{
return controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Where(m => !((MethodWrapper)m).Method.IsSpecialName)
diff --git a/TypeScript.ContractGenerator.Tests/OptionsTests.cs b/TypeScript.ContractGenerator.Tests/OptionsTests.cs
index 758c5db..e8c489d 100644
--- a/TypeScript.ContractGenerator.Tests/OptionsTests.cs
+++ b/TypeScript.ContractGenerator.Tests/OptionsTests.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using System.Text.RegularExpressions;
using NUnit.Framework;
@@ -23,6 +24,12 @@ public void OptionalPropertiesTest(bool optionalPropertiesEnabled, string expect
generatedCode.Diff(expectedCode).ShouldBeEmpty();
}
+ [Test]
+ public void v()
+ {
+ var prms = Regex.Matches("/v1/user/{userId}/blog", @"{(\w+)}");
+ }
+
[TestCase(NullabilityMode.Pessimistic, "explicit-nullability-enabled")]
[TestCase(NullabilityMode.None, "explicit-nullability-disabled")]
public void ExplicitNullabilityTest(NullabilityMode nullabilityMode, string expectedFileName)
diff --git a/TypeScript.ContractGenerator/CodeDom/TypeScriptTemplateStringLiteral.cs b/TypeScript.ContractGenerator/CodeDom/TypeScriptTemplateStringLiteral.cs
index bc1040f..1c1ceeb 100644
--- a/TypeScript.ContractGenerator/CodeDom/TypeScriptTemplateStringLiteral.cs
+++ b/TypeScript.ContractGenerator/CodeDom/TypeScriptTemplateStringLiteral.cs
@@ -2,16 +2,19 @@ namespace SkbKontur.TypeScript.ContractGenerator.CodeDom
{
public class TypeScriptTemplateStringLiteral : TypeScriptExpression
{
- public TypeScriptTemplateStringLiteral(string value)
+ public TypeScriptTemplateStringLiteral(string value, TypeScriptVariableReference? tag = null)
{
Value = value;
+ Tag = tag;
}
+ public TypeScriptVariableReference? Tag { get; }
public string Value { get; }
public override string GenerateCode(ICodeGenerationContext context)
{
- return $"`{Value}`";
+ var tag = Tag == null ? string.Empty : Tag.GenerateCode(context);
+ return $"{tag}`{Value}`";
}
}
}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContext.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContext.cs
index 81e7af8..ab1d326 100644
--- a/TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContext.cs
+++ b/TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContext.cs
@@ -1,138 +1,140 @@
using System;
+using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
using SkbKontur.TypeScript.ContractGenerator.Abstractions;
using SkbKontur.TypeScript.ContractGenerator.CodeDom;
-using SkbKontur.TypeScript.ContractGenerator.Extensions;
-
-using TypeInfo = SkbKontur.TypeScript.ContractGenerator.Internals.TypeInfo;
namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
{
- public class ApiControllerTypeBuildingContext : ApiControllerTypeBuildingContextBase
+ public class ApiControllerTypeBuildingContext : TypeBuildingContext
{
- public ApiControllerTypeBuildingContext(TypeScriptUnit unit, ITypeInfo type)
+ public ApiControllerTypeBuildingContext(TypeScriptUnit unit, ITypeInfo type, IApiCustomization? apiCustomization = null)
: base(unit, type)
{
+ ApiCustomization = apiCustomization ?? new DefaultApiCustomization();
}
- protected override TypeLocation GetApiBase(ITypeInfo controllerType)
- {
- return new TypeLocation
- {
- Name = "ApiBase",
- Location = "ApiBase/ApiBase",
- };
- }
+ protected IApiCustomization ApiCustomization { get; }
+ protected Func BuildAndImportType { get; private set; }
- protected override ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
+ public override void Initialize(ITypeGenerator typeGenerator)
{
- if (typeInfo.IsGenericType)
- {
- var genericTypeDefinition = typeInfo.GetGenericTypeDefinition();
- if (genericTypeDefinition.Equals(TypeInfo.From(typeof(Task<>))) ||
- genericTypeDefinition.Name == KnownTypeNames.ActionResultOfT ||
- genericTypeDefinition.Name == KnownTypeNames.ActionResult)
- return ResolveReturnType(typeInfo.GetGenericArguments()[0]);
- }
-
- if (typeInfo.Equals(TypeInfo.From()) || typeInfo.Name == KnownTypeNames.ActionResult)
- return TypeInfo.From(typeof(void));
+ BuildAndImportType = t => typeGenerator.BuildAndImportType(Unit, t);
- return typeInfo;
- }
-
- protected override BaseApiMethod ResolveBaseApiMethod(IMethodInfo methodInfo)
- {
- var attributes = methodInfo.GetAttributes(inherit : false);
+ var baseApi = ApiCustomization.GetApiBase(Type);
+ var urlTag = ApiCustomization.GetUrlTag(Type);
- if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpGet)))
- return BaseApiMethod.Get;
+ Unit.AddSymbolImport(baseApi.Name, baseApi.Location);
+ Unit.AddSymbolImport(urlTag.Name, urlTag.Location);
- if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpPost)))
- return BaseApiMethod.Post;
+ var apiName = ApiCustomization.GetApiClassName(Type);
+ var interfaceName = ApiCustomization.GetApiInterfaceName(Type);
+ var apiMethods = ApiCustomization.GetApiMethods(Type);
- if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpPut)))
- return BaseApiMethod.Put;
+ var apiInterfaceDefinition = new TypeScriptInterfaceDefinition();
+ apiInterfaceDefinition.Members.AddRange(apiMethods.SelectMany(BuildApiInterfaceMember));
- if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpDelete)))
- return BaseApiMethod.Delete;
-
- if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpPatch)))
- return BaseApiMethod.Patch;
-
- throw new NotSupportedException(
- $"Unresolved http verb for method {methodInfo.Name} at controller {methodInfo.DeclaringType?.Name}");
- }
-
- protected override string BuildRoute(ITypeInfo controllerType, IMethodInfo methodInfo)
- {
- var fullRoute = RouteTemplateHelper.FindFullRouteTemplate(controllerType, methodInfo)?.ValueWithoutConstraints ?? "";
- if (fullRoute.StartsWith("/"))
- return fullRoute;
+ var apiClassDefinition = new TypeScriptClassDefinition
+ {
+ BaseClass = new TypeScriptTypeReference(baseApi.Name),
+ ImplementedInterfaces = new TypeScriptType[] {new TypeScriptTypeReference(interfaceName)},
+ };
- return "/" + fullRoute;
- }
+ apiClassDefinition.Members.AddRange(apiMethods.SelectMany(BuildApiImplMember));
- protected override IParameterInfo[] GetQueryParameters(IParameterInfo[] parameters, ITypeInfo controllerType)
- {
- return parameters.Where(x => PassParameterToCall(x, controllerType) && !IsFromBody(x))
- .ToArray();
- }
+ Unit.Body.Add(new TypeScriptExportStatement
+ {
+ Declaration = new TypeScriptClassDeclaration
+ {
+ Name = apiName,
+ Defintion = apiClassDefinition
+ }
+ });
+
+ foreach (var statement in BuildAdditionalStatements())
+ {
+ Unit.Body.Add(statement);
+ }
- protected override IParameterInfo? GetBody(IParameterInfo[] parameters, ITypeInfo controllerType)
- {
- return parameters.SingleOrDefault(x => PassParameterToCall(x, controllerType) && IsFromBody(x));
- }
+ Declaration = new TypeScriptInterfaceDeclaration
+ {
+ Name = interfaceName,
+ Definition = apiInterfaceDefinition
+ };
- protected override IMethodInfo[] GetMethodsToImplement(ITypeInfo controllerType)
- {
- return controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
- .Where(m => m.GetAttributes(inherit : false)
- .Any(a => KnownTypeNames.HttpAttributeNames.Any(a.HasName)))
- .ToArray();
+ base.Initialize(typeGenerator);
}
- protected override bool PassParameterToCall(IParameterInfo parameterInfo, ITypeInfo controllerType)
+ protected virtual IEnumerable BuildApiImplMember(IMethodInfo methodInfo)
{
- return !parameterInfo.ParameterType.Equals(TypeInfo.From());
- }
+ var functionDefinition = new TypeScriptFunctionDefinition
+ {
+ IsAsync = ApiCustomization.IsAsyncMethod(methodInfo),
+ Result = ApiCustomization.GetMethodResultType(methodInfo, BuildAndImportType),
+ Body = {CreateCall(methodInfo)}
+ };
- protected override string GetMethodName(IMethodInfo methodInfo)
- {
- return IsUrlMethod(methodInfo) ? $"get{methodInfo.Name}Url" : methodInfo.Name.ToLowerCamelCase();
+ functionDefinition.Arguments.AddRange(
+ ApiCustomization
+ .GetMethodParameters(methodInfo)
+ .Select(x => new TypeScriptArgumentDeclaration
+ {
+ Name = x.Name,
+ Type = BuildAndImportType(x.ParameterType)
+ })
+ );
+
+ yield return new TypeScriptClassMemberDefinition
+ {
+ Name = ApiCustomization.GetMethodName(methodInfo),
+ Definition = functionDefinition
+ };
}
- protected override TypeScriptReturnStatement CreateCall(IMethodInfo methodInfo, ITypeInfo controllerType)
+ protected virtual TypeScriptReturnStatement CreateCall(IMethodInfo methodInfo)
{
- if (!IsUrlMethod(methodInfo))
- return base.CreateCall(methodInfo, controllerType);
+ var route = ApiCustomization.GetMethodRoute(methodInfo);
+ var routeExpression = new TypeScriptTemplateStringLiteral(route.Replace("{", "${"), new TypeScriptVariableReference("url"));
- var routeTemplate = BuildRoute(controllerType, methodInfo);
- return new TypeScriptReturnStatement(new TypeScriptTemplateStringLiteral(routeTemplate.Replace("{", "${")));
- }
+ if (ApiCustomization.IsUrlMethod(methodInfo))
+ {
+ return new TypeScriptReturnStatement(routeExpression);
+ }
- protected override bool IsAsyncMethod(IMethodInfo methodInfo)
- {
- return !IsUrlMethod(methodInfo);
- }
+ var bodyExpression = ApiCustomization.GetMethodBodyExpression(methodInfo);
+ var arguments = bodyExpression == null
+ ? new[] {routeExpression}
+ : new[] {routeExpression, bodyExpression};
- protected override TypeScriptType? ResolveReturnType(IMethodInfo methodInfo, Func buildAndImportType)
- {
- return IsUrlMethod(methodInfo) ? new TypeScriptTypeReference("string") : null;
+ return new TypeScriptReturnStatement(
+ new TypeScriptMethodCallExpression(new TypeScriptThisReference(), ApiCustomization.GetMethodVerb(methodInfo), arguments)
+ );
}
- private static bool IsFromBody(IParameterInfo parameterInfo)
+ protected virtual IEnumerable BuildApiInterfaceMember(IMethodInfo methodInfo)
{
- return parameterInfo.GetAttributes(inherit : false).Any(x => x.HasName(KnownTypeNames.Attributes.FromBody));
+ var result = new TypeScriptInterfaceFunctionMember(
+ ApiCustomization.GetMethodName(methodInfo),
+ ApiCustomization.GetMethodResultType(methodInfo, BuildAndImportType)
+ );
+
+ result.Arguments.AddRange(
+ ApiCustomization
+ .GetMethodParameters(methodInfo)
+ .Select(x => new TypeScriptArgumentDeclaration
+ {
+ Name = x.Name,
+ Type = BuildAndImportType(x.ParameterType)
+ })
+ );
+
+ yield return result;
}
- private static bool IsUrlMethod(IMethodInfo methodInfo)
+ protected virtual IEnumerable BuildAdditionalStatements()
{
- return methodInfo.GetAttributes(TypeInfo.From()).Any();
+ yield break;
}
}
}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContextBase.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContextBase.cs
deleted file mode 100644
index 0da2a77..0000000
--- a/TypeScript.ContractGenerator/TypeBuilders/ApiController/ApiControllerTypeBuildingContextBase.cs
+++ /dev/null
@@ -1,225 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-
-using SkbKontur.TypeScript.ContractGenerator.Abstractions;
-using SkbKontur.TypeScript.ContractGenerator.CodeDom;
-using SkbKontur.TypeScript.ContractGenerator.Extensions;
-using SkbKontur.TypeScript.ContractGenerator.Internals;
-
-namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
-{
- public abstract class ApiControllerTypeBuildingContextBase : TypeBuildingContext
- {
- public ApiControllerTypeBuildingContextBase(TypeScriptUnit unit, ITypeInfo type)
- : base(unit, type)
- {
- }
-
- protected virtual TypeLocation GetApiBase(ITypeInfo controllerType)
- {
- return new TypeLocation
- {
- Name = "ApiBase",
- Location = "../apiBase/ApiBase",
- };
- }
-
- protected virtual string GetApiName(ITypeInfo controllerType)
- {
- return new Regex("(Api)?Controller").Replace(controllerType.Name, "Api");
- }
-
- protected virtual ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
- {
- if (typeInfo.IsGenericType)
- {
- var genericTypeDefinition = typeInfo.GetGenericTypeDefinition();
- if (genericTypeDefinition.Equals(TypeInfo.From(typeof(Task<>))))
- return ResolveReturnType(typeInfo.GetGenericArguments()[0]);
- }
-
- if (typeInfo.Equals(TypeInfo.From()))
- return TypeInfo.From(typeof(void));
- return typeInfo;
- }
-
- protected virtual TypeScriptType? ResolveReturnType(IMethodInfo methodInfo, Func buildAndImportType)
- {
- return null;
- }
-
- protected virtual string GetMethodName(IMethodInfo methodInfo)
- {
- return methodInfo.Name.ToLowerCamelCase();
- }
-
- protected virtual bool IsAsyncMethod(IMethodInfo methodInfo)
- {
- return true;
- }
-
- protected virtual bool PassParameterToCall(IParameterInfo parameterInfo, ITypeInfo controllerType) => true;
-
- protected virtual TypeScriptStatement WrapCall(IMethodInfo methodInfo, TypeScriptReturnStatement call) => call;
-
- protected virtual string GetApiClassName(string apiName) => apiName;
-
- protected abstract BaseApiMethod ResolveBaseApiMethod(IMethodInfo methodInfo);
- protected abstract string BuildRoute(ITypeInfo controllerType, IMethodInfo methodInfo);
- protected abstract IParameterInfo[] GetQueryParameters(IParameterInfo[] parameters, ITypeInfo controllerType);
- protected abstract IParameterInfo? GetBody(IParameterInfo[] parameters, ITypeInfo controllerType);
- protected abstract IMethodInfo[] GetMethodsToImplement(ITypeInfo controllerType);
-
- public override void Initialize(ITypeGenerator typeGenerator)
- {
- Declaration = GenerateInternalApiController(Unit, Type, x => typeGenerator.BuildAndImportType(Unit, x));
- base.Initialize(typeGenerator);
- }
-
- private TypeScriptTypeDeclaration GenerateInternalApiController(TypeScriptUnit targetUnit, ITypeInfo type, Func buildAndImportType)
- {
- var baseApi = GetApiBase(type);
- var apiName = GetApiName(type);
- var interfaceName = "I" + apiName;
- var methodInfos = GetMethodsToImplement(type);
-
- var definition = new TypeScriptInterfaceDefinition();
- definition.Members.AddRange(methodInfos
- .SelectMany(x => BuildApiInterfaceMember(x, buildAndImportType, type)));
- targetUnit.AddSymbolImport(baseApi.Name, baseApi.Location);
-
- var interfaceDeclaration = new TypeScriptInterfaceDeclaration
- {
- Name = interfaceName,
- Definition = definition
- };
- var typeScriptClassDefinition = new TypeScriptClassDefinition
- {
- BaseClass = new TypeScriptTypeReference(baseApi.Name),
- ImplementedInterfaces = new TypeScriptType[] {new TypeScriptTypeReference(interfaceName)},
- };
-
- typeScriptClassDefinition.Members.AddRange(
- methodInfos
- .SelectMany(x => BuildApiImplMember(x, buildAndImportType, type)));
-
- targetUnit.Body.Add(new TypeScriptExportStatement
- {
- Declaration = new TypeScriptClassDeclaration
- {
- Name = GetApiClassName(apiName),
- Defintion = typeScriptClassDefinition
- }
- });
-
- return interfaceDeclaration;
- }
-
- protected virtual IEnumerable BuildApiImplMember(IMethodInfo methodInfo, Func buildAndImportType, ITypeInfo controllerType)
- {
- var functionDefinition = new TypeScriptFunctionDefinition
- {
- IsAsync = IsAsyncMethod(methodInfo),
- Result = GetMethodResult(methodInfo, buildAndImportType),
- Body = {WrapCall(methodInfo, CreateCall(methodInfo, controllerType))}
- };
- functionDefinition.Arguments.AddRange(
- methodInfo.GetParameters().Where(x => PassParameterToCall(x, controllerType)).Select(x => new TypeScriptArgumentDeclaration
- {
- Name = x.Name,
- Type = buildAndImportType(x.ParameterType)
- })
- );
- yield return
- new TypeScriptClassMemberDefinition
- {
- Name = GetMethodName(methodInfo),
- Definition = functionDefinition
- };
- }
-
- private TypeScriptType GetMethodResult(IMethodInfo methodInfo, Func buildAndImportType)
- {
- return ResolveReturnType(methodInfo, buildAndImportType) ?? new TypeScriptPromiseOfType(buildAndImportType(ResolveReturnType(methodInfo.ReturnType)));
- }
-
- protected virtual TypeScriptReturnStatement CreateCall(IMethodInfo methodInfo, ITypeInfo controllerType)
- {
- var verb = ResolveBaseApiMethod(methodInfo);
- return GenerateMethodCallWithBody(methodInfo, $"make{verb}Request", controllerType);
- }
-
- private TypeScriptReturnStatement GenerateMethodCallWithBody(IMethodInfo methodInfo, string methodName, ITypeInfo controllerType)
- {
- var route = BuildRoute(controllerType, methodInfo);
- return new TypeScriptReturnStatement(
- new TypeScriptMethodCallExpression(
- new TypeScriptThisReference(),
- methodName,
- new TypeScriptTemplateStringLiteral(route.Replace("{", "${")),
- GenerateConstructGetParams(
- GetQueryParameters(methodInfo.GetParameters(), controllerType),
- route
- ),
- GetBodyExpression(methodInfo, methodName, controllerType)
- ));
- }
-
- private TypeScriptExpression GetBodyExpression(IMethodInfo methodInfo, string methodName, ITypeInfo controllerType)
- {
- return GenerateCustomBody(methodInfo, methodName, controllerType) ??
- GenerateConstructBody(GetBody(methodInfo.GetParameters(), controllerType));
- }
-
- private static TypeScriptExpression GenerateConstructBody(IParameterInfo? parameter)
- {
- if (parameter == null)
- return new TypeScriptObjectLiteral();
-
- if (parameter.ParameterType.IsArray)
- return new TypeScriptVariableReference(parameter.Name);
-
- return new TypeScriptObjectLiteral(
- new TypeScriptObjectLiteralSpread(new TypeScriptVariableReference(parameter.Name))
- );
- }
-
- protected virtual TypeScriptExpression? GenerateCustomBody(IMethodInfo methodInfo, string methodName, ITypeInfo controllerType)
- {
- return null;
- }
-
- private static TypeScriptExpression GenerateConstructGetParams(IParameterInfo[] parameters, string routeTemplate)
- {
- var literalProperties = parameters
- .Where(x => !routeTemplate.Contains("{" + x.Name + "}"))
- .Select(parameter => (TypeScriptObjectLiteralInitializer)new TypeScriptObjectLiteralProperty(
- new TypeScriptStringLiteral(parameter.Name),
- new TypeScriptVariableReference(parameter.Name)))
- .ToArray();
- var result = new TypeScriptObjectLiteral(literalProperties);
- return result;
- }
-
- protected virtual IEnumerable BuildApiInterfaceMember(IMethodInfo methodInfo, Func buildAndImportType, ITypeInfo controllerType)
- {
- var result = new TypeScriptInterfaceFunctionMember(
- GetMethodName(methodInfo),
- GetMethodResult(methodInfo, buildAndImportType)
- );
- result.Arguments.AddRange(
- methodInfo.GetParameters()
- .Where(x => PassParameterToCall(x, controllerType))
- .Select(x => new TypeScriptArgumentDeclaration
- {
- Name = x.Name,
- Type = buildAndImportType(x.ParameterType)
- })
- );
- yield return result;
- }
- }
-}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/BaseApiMethod.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/BaseApiMethod.cs
deleted file mode 100644
index 4e74232..0000000
--- a/TypeScript.ContractGenerator/TypeBuilders/ApiController/BaseApiMethod.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
-{
- public enum BaseApiMethod
- {
- Get,
- Post,
- Put,
- Delete,
- Download,
- Upload,
- Patch
- }
-}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/DefaultApiCustomization.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/DefaultApiCustomization.cs
new file mode 100644
index 0000000..a94d583
--- /dev/null
+++ b/TypeScript.ContractGenerator/TypeBuilders/ApiController/DefaultApiCustomization.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+
+using SkbKontur.TypeScript.ContractGenerator.Abstractions;
+using SkbKontur.TypeScript.ContractGenerator.CodeDom;
+using SkbKontur.TypeScript.ContractGenerator.Extensions;
+
+using TypeInfo = SkbKontur.TypeScript.ContractGenerator.Internals.TypeInfo;
+
+namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
+{
+ public class DefaultApiCustomization : IApiCustomization
+ {
+ public TypeLocation GetApiBase(ITypeInfo type) => new TypeLocation
+ {
+ Name = "ApiBase",
+ Location = "ApiBase/ApiBase",
+ };
+
+ public TypeLocation GetUrlTag(ITypeInfo type) => new TypeLocation
+ {
+ Name = "url",
+ Location = "ApiBase/ApiBase",
+ };
+
+ public string GetApiClassName(ITypeInfo type) => new Regex("(Api)?Controller").Replace(type.Name, "Api");
+ public string GetApiInterfaceName(ITypeInfo type) => "I" + GetApiClassName(type);
+
+ public string GetMethodName(IMethodInfo methodInfo) => IsUrlMethod(methodInfo)
+ ? $"urlFor{methodInfo.Name}"
+ : methodInfo.Name.ToLowerCamelCase();
+
+ public IMethodInfo[] GetApiMethods(ITypeInfo type) =>
+ type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
+ .Where(m => m.GetAttributes(inherit : false).Any(a => KnownTypeNames.HttpAttributeNames.Any(a.HasName)))
+ .ToArray();
+
+ public IParameterInfo[] GetMethodParameters(IMethodInfo methodInfo) =>
+ methodInfo.GetParameters()
+ .Where(x => !x.ParameterType.Equals(TypeInfo.From()))
+ .ToArray();
+
+ public bool IsUrlMethod(IMethodInfo methodInfo)
+ {
+ return GetMethodVerb(methodInfo) == "makeGetRequest" && ResolveReturnType(methodInfo.ReturnType).Equals(TypeInfo.From(typeof(void)));
+ }
+
+ public bool IsAsyncMethod(IMethodInfo methodInfo) => false;
+
+ public string GetMethodVerb(IMethodInfo methodInfo)
+ {
+ var attributes = methodInfo.GetAttributes(inherit : false);
+
+ if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpGet)))
+ return "makeGetRequest";
+
+ if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpPost)))
+ return "makePostRequest";
+
+ if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpPut)))
+ return "makePutRequest";
+
+ if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpDelete)))
+ return "makeDeleteRequest";
+
+ if (attributes.Any(x => x.HasName(KnownTypeNames.Attributes.HttpPatch)))
+ return "makePatchRequest";
+
+ throw new NotSupportedException($"Unresolved http verb for method {methodInfo.Name} at controller {methodInfo.DeclaringType?.Name}");
+ }
+
+ public string GetMethodRoute(IMethodInfo methodInfo)
+ {
+ var path = RouteTemplateHelper.GetRouteTemplate(methodInfo.DeclaringType!, methodInfo);
+
+ var routeParameters = new HashSet(
+ Regex.Matches(path, @"{(\w+)}")
+ .Cast()
+ .Where(x => x.Success && x.Groups.Count > 1 && x.Groups[1].Success)
+ .Select(x => x.Groups[1].Value)
+ );
+
+ var queryParams = GetMethodParameters(methodInfo)
+ .Where(x => !IsFromBody(x) && !routeParameters.Contains(x.Name))
+ .Select(x => $"{x.Name}={{{x.Name}}}")
+ .ToArray();
+
+ var query = queryParams.Any()
+ ? "?" + string.Join("&", queryParams)
+ : string.Empty;
+
+ return $"{path}{query}";
+ }
+
+ public TypeScriptType GetMethodResultType(IMethodInfo methodInfo, Func buildAndImportType)
+ {
+ return IsUrlMethod(methodInfo)
+ ? (TypeScriptType)new TypeScriptTypeReference("string")
+ : new TypeScriptPromiseOfType(buildAndImportType(ResolveReturnType(methodInfo.ReturnType)));
+ }
+
+ public TypeScriptExpression? GetMethodBodyExpression(IMethodInfo methodInfo)
+ {
+ var parameter = GetMethodParameters(methodInfo).SingleOrDefault(IsFromBody);
+ return parameter == null
+ ? null
+ : new TypeScriptVariableReference(parameter.Name);
+ }
+
+ private static bool IsFromBody(IParameterInfo parameterInfo)
+ {
+ return parameterInfo
+ .GetAttributes(inherit : false)
+ .Any(a => a.HasName(KnownTypeNames.Attributes.FromBody));
+ }
+
+ private static ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
+ {
+ if (typeInfo.IsGenericType)
+ {
+ var genericTypeDefinition = typeInfo.GetGenericTypeDefinition();
+ if (genericTypeDefinition.Equals(TypeInfo.From(typeof(Task<>))) ||
+ genericTypeDefinition.Name == KnownTypeNames.ActionResultOfT ||
+ genericTypeDefinition.Name == KnownTypeNames.ActionResult)
+ return ResolveReturnType(typeInfo.GetGenericArguments()[0]);
+ }
+
+ if (typeInfo.Equals(TypeInfo.From()) ||
+ typeInfo.Name == KnownTypeNames.ActionResult ||
+ typeInfo.Name == KnownTypeNames.IActionResult)
+ return TypeInfo.From(typeof(void));
+
+ return typeInfo;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/IApiCustomization.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/IApiCustomization.cs
new file mode 100644
index 0000000..541a3e0
--- /dev/null
+++ b/TypeScript.ContractGenerator/TypeBuilders/ApiController/IApiCustomization.cs
@@ -0,0 +1,27 @@
+using System;
+
+using SkbKontur.TypeScript.ContractGenerator.Abstractions;
+using SkbKontur.TypeScript.ContractGenerator.CodeDom;
+
+namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
+{
+ public interface IApiCustomization
+ {
+ public TypeLocation GetApiBase(ITypeInfo type);
+ public TypeLocation GetUrlTag(ITypeInfo type);
+
+ string GetApiClassName(ITypeInfo type);
+ string GetApiInterfaceName(ITypeInfo type);
+ string GetMethodName(IMethodInfo methodInfo);
+
+ IMethodInfo[] GetApiMethods(ITypeInfo type);
+ IParameterInfo[] GetMethodParameters(IMethodInfo methodInfo);
+
+ bool IsUrlMethod(IMethodInfo methodInfo);
+ bool IsAsyncMethod(IMethodInfo methodInfo);
+ string GetMethodVerb(IMethodInfo methodInfo);
+ string GetMethodRoute(IMethodInfo methodInfo);
+ TypeScriptType GetMethodResultType(IMethodInfo methodInfo, Func buildAndImportType);
+ TypeScriptExpression? GetMethodBodyExpression(IMethodInfo methodInfo);
+ }
+}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/KnownTypeNames.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/KnownTypeNames.cs
index b821bb3..fb2a994 100644
--- a/TypeScript.ContractGenerator/TypeBuilders/ApiController/KnownTypeNames.cs
+++ b/TypeScript.ContractGenerator/TypeBuilders/ApiController/KnownTypeNames.cs
@@ -6,6 +6,7 @@ public static class KnownTypeNames
{
public const string ActionResultOfT = "ActionResult`1";
public const string ActionResult = "ActionResult";
+ public const string IActionResult = "IActionResult";
public static readonly HashSet HttpAttributeNames = new HashSet
{
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplate.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplate.cs
deleted file mode 100644
index 0d19f0e..0000000
--- a/TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplate.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-
-namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
-{
- public class RouteTemplate : IEquatable
- {
- public RouteTemplate(string value)
- {
- this.value = value;
- ParameterNames = GetParameterNames(value);
- ValueWithoutConstraints = Regex.Replace(value, @"{(\w+):\w+}", "{$1}");
- }
-
- public HashSet ParameterNames { get; }
-
- public string ValueWithoutConstraints { get; }
-
- public override string ToString() => value;
-
- private static HashSet GetParameterNames(string routeTemplate)
- {
- return new HashSet(
- Regex
- .Matches(routeTemplate, @"{(\w+):\w+}")
- .Cast()
- .Where(x => x.Success)
- .Where(x => x.Groups.Count > 1)
- .Where(x => x.Groups[1].Success)
- .Select(x => x.Groups[1].Value));
- }
-
- public bool Equals(RouteTemplate? other)
- {
- if (ReferenceEquals(null, other))
- {
- return false;
- }
-
- if (ReferenceEquals(this, other))
- {
- return true;
- }
-
- return value == other.value;
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj))
- {
- return false;
- }
-
- if (ReferenceEquals(this, obj))
- {
- return true;
- }
-
- if (obj.GetType() != GetType())
- {
- return false;
- }
-
- return Equals((RouteTemplate)obj);
- }
-
- public override int GetHashCode()
- {
- return value.GetHashCode();
- }
-
- private readonly string value;
- }
-}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplateHelper.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplateHelper.cs
index 49fe4bf..6e6ac6a 100644
--- a/TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplateHelper.cs
+++ b/TypeScript.ContractGenerator/TypeBuilders/ApiController/RouteTemplateHelper.cs
@@ -8,7 +8,18 @@ namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
{
public static class RouteTemplateHelper
{
- public static RouteTemplate? FindFullRouteTemplate(ITypeInfo controller, IMethodInfo action)
+ public static string GetRouteTemplate(ITypeInfo controller, IMethodInfo action)
+ {
+ var rawTemplate = FindFullRouteTemplate(controller, action);
+
+ var valueWithoutConstraints = Regex.Replace(rawTemplate, @"{(\w+):\w+}", "{$1}");
+ if (valueWithoutConstraints.StartsWith("/"))
+ return valueWithoutConstraints;
+
+ return "/" + valueWithoutConstraints;
+ }
+
+ private static string FindFullRouteTemplate(ITypeInfo controller, IMethodInfo action)
{
var routePrefix = (controller
.GetAttributes(inherit : true)
@@ -26,15 +37,15 @@ public static class RouteTemplateHelper
.Replace("[action]", action.Name);
if (routeTemplate.StartsWith("~/"))
- return new RouteTemplate(Regex.Replace(routeTemplate, "^~/?", ""));
+ return Regex.Replace(routeTemplate, "^~/?", "");
if (string.IsNullOrEmpty(routePrefix))
- return new RouteTemplate(routeTemplate);
+ return routeTemplate;
if (string.IsNullOrEmpty(routeTemplate))
- return new RouteTemplate(routePrefix);
+ return routePrefix;
- return new RouteTemplate($"{routePrefix}/{routeTemplate}");
+ return $"{routePrefix}/{routeTemplate}";
}
}
}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/ApiController/UrlOnlyAttribute.cs b/TypeScript.ContractGenerator/TypeBuilders/ApiController/UrlOnlyAttribute.cs
deleted file mode 100644
index b3baa60..0000000
--- a/TypeScript.ContractGenerator/TypeBuilders/ApiController/UrlOnlyAttribute.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController
-{
- public class UrlOnlyAttribute : Attribute
- {
- }
-}
\ No newline at end of file