Skip to content

Commit

Permalink
Merge pull request #69 from skbkontur/refactor-api-building-context
Browse files Browse the repository at this point in the history
use string templates for urls
  • Loading branch information
fakefeik authored Jan 11, 2024
2 parents 237c802 + 3325820 commit 18dd4b8
Show file tree
Hide file tree
Showing 19 changed files with 389 additions and 539 deletions.
19 changes: 15 additions & 4 deletions AspNetCoreExample.Api/Controllers/WeatherForecastController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

using Microsoft.AspNetCore.Mvc;

using SkbKontur.TypeScript.ContractGenerator.TypeBuilders.ApiController;

namespace AspNetCoreExample.Api.Controllers;

[ApiController]
Expand Down Expand Up @@ -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)
{
Expand All @@ -53,6 +51,19 @@ public ActionResult Download(string city)
return File(JsonSerializer.SerializeToUtf8Bytes(forecast), "application/json");
}

[HttpGet("{street}/view")]
public async Task<ActionResult> GetStreetView(string street, bool useGoogleImages)
{
await Task.Delay(100);
return File(Array.Empty<byte>(), "image/jpeg");
}

[HttpGet("none")]
public Task<ActionResult<Guid>> NewGuid()
{
return Task.FromResult((ActionResult<Guid>)Ok(Guid.NewGuid()));
}

private static readonly string[] summaries =
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
Expand Down
15 changes: 5 additions & 10 deletions AspNetCoreExample.Generator/output/api/NotesApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
return this.makePostRequest(`/v1/user/${userId}/blog`, {

}, {
...entry,
});
addEntry(userId: Guid, entry: BlogEntry): Promise<void> {
return this.makePostRequest(url`/v1/user/${userId}/blog`, entry);
}

async addEntries(userId: Guid, entries: BlogEntry[]): Promise<void> {
return this.makePostRequest(`/v1/user/${userId}/blog/batch`, {

}, entries);
addEntries(userId: Guid, entries: BlogEntry[]): Promise<void> {
return this.makePostRequest(url`/v1/user/${userId}/blog/batch`, entries);
}

};
Expand Down
33 changes: 9 additions & 24 deletions AspNetCoreExample.Generator/output/api/UserApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
return this.makePostRequest(`/v1/users`, {

}, {
...user,
});
createUser(user: User): Promise<void> {
return this.makePostRequest(url`/v1/users`, user);
}

async deleteUser(userId: Guid): Promise<void> {
return this.makeDeleteRequest(`/v1/users/${userId}`, {

}, {

});
deleteUser(userId: Guid): Promise<void> {
return this.makeDeleteRequest(url`/v1/users/${userId}`);
}

async getUser(userId: Guid): Promise<User> {
return this.makeGetRequest(`/v1/users/${userId}`, {

}, {

});
getUser(userId: Guid): Promise<User> {
return this.makeGetRequest(url`/v1/users/${userId}`);
}

async searchUsers(name: string): Promise<User[]> {
return this.makeGetRequest(`/v1/users`, {
['name']: name,
}, {

});
searchUsers(name: string): Promise<User[]> {
return this.makeGetRequest(url`/v1/users?name=${name}`);
}

};
Expand Down
42 changes: 21 additions & 21 deletions AspNetCoreExample.Generator/output/api/WeatherForecastApi.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
// 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<WeatherForecast[]> {
return this.makeGetRequest(`/WeatherForecast`, {

}, {

});
get(): Promise<WeatherForecast[]> {
return this.makeGetRequest(url`/WeatherForecast`);
}

async update(city: string, forecast: WeatherForecast): Promise<void> {
return this.makePostRequest(`/WeatherForecast/Update/${city}`, {

}, {
...forecast,
});
update(city: string, forecast: WeatherForecast): Promise<void> {
return this.makePostRequest(url`/WeatherForecast/Update/${city}`, forecast);
}

async reset(seed: number): Promise<void> {
return this.makePostRequest(`/Reset`, {
['seed']: seed,
}, {

});
reset(seed: number): Promise<void> {
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<Guid> {
return this.makeGetRequest(url`/WeatherForecast/none`);
}

};
export interface IWeatherForecastApi {
get(): Promise<WeatherForecast[]>;
update(city: string, forecast: WeatherForecast): Promise<void>;
reset(seed: number): Promise<void>;
getDownloadUrl(city: string): string;
urlForDownload(city: string): string;
urlForGetStreetView(street: string, useGoogleImages: boolean): string;
newGuid(): Promise<Guid>;
}
37 changes: 12 additions & 25 deletions AspNetCoreExample.Generator/output/apiBase/ApiBase.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,45 @@
/* 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<any> {
const response = await fetch(this.getUrl(url, queryParams), {
public async makeGetRequest(url: string, body?: any): Promise<any> {
const response = await fetch(url, {
method: "GET",
});
return await response.json();
}

public async makePostRequest(url: string, queryParams: IParamsMap, body: any): Promise<any> {
const response = await fetch(this.getUrl(url, queryParams), {
public async makePostRequest(url: string, body?: any): Promise<any> {
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(body),
body: body && JSON.stringify(body),
});
const textResult = await response.text();
if (textResult !== "") {
return JSON.parse(textResult);
}
}

public async makePutRequest(url: string, queryParams: IParamsMap, body: any): Promise<any> {
const response = await fetch(this.getUrl(url, queryParams), {
public async makePutRequest(url: string, body?: any): Promise<any> {
const response = await fetch(url, {
method: "PUT",
body: JSON.stringify(body),
body: body && JSON.stringify(body),
});
const textResult = await response.text();
if (textResult !== "") {
return JSON.parse(textResult);
}
}

public async makeDeleteRequest(url: string, queryParams: IParamsMap, body: any): Promise<any> {
const response = await fetch(this.getUrl(url, queryParams), {
public async makeDeleteRequest(url: string, body?: any): Promise<any> {
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)}`;
}
}
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# Changelog
## v2.1 - 2024.01.11
- refactor ApiControllerTypeBuildingContext
- use string templates with custom tag for urls in generated apis

## v2.0.150 - 2024.01.09
- Add net8.0 support to cli tool; run tests against net8.0 tfm
- Fix tests to run on Linux

## v2.0.142 - 2023.11.09
- support type imports

## v2.0.135 - 2023.09.19
- Update `Microsoft.CodeAnalysis.CSharp.Workspaces`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -46,7 +46,7 @@ namespace AspNetCoreExample.Generator
return "ApiBase";
}

protected override ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
protected ITypeInfo ResolveReturnType(ITypeInfo typeInfo)
{
if (typeInfo.IsGenericType)
{
Expand All @@ -62,49 +62,49 @@ 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)
.SingleOrDefault(x => !string.IsNullOrEmpty(x));
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)
Expand Down
Loading

0 comments on commit 18dd4b8

Please sign in to comment.