This repository contains a preview version of a .Net code generator for API consuming documented by Swagger or Open API.
dotnet tool install -g Swag4Net.ClientGenerator --version 1.0.0-CI00001 --add-source https://www.myget.org/F/romcyber/api/v3/index.jsonfake buildUsage:
When your swagger spec is stored in a local file:
*.exe --specfile ..\Files\petstore.json --outputfolder ../Swag4Net.Sample/Generated --namespace Swag4Net.Sample.Generated --clientname PetstoreClientWhen your swagger spec is downloadable from an endpoint:
*.exe --specfile http://localhost:50464/swagger/v1/swagger.json --outputfolder ../Swag4Net.Sample/Generated --namespace Swag4Net.Sample.Generated --clientname PetstoreClientIn following points, I will describe the main motivations of this project.
All REST clients inherit from a client base maintained in another assembly.
Generated code have to be simplest !
Generated client's methods return a Result.
Imagine an use case with messages API.
A generated method for message sending should be:
public Task<Result> Send(SendMessageRequest messageRequest, CancellationToken cancellationToken = default(CancellationToken))
{
var request = new HttpRequestMessage(HttpMethod.Post, "/api/Messages/Send");
base.AddBodyParameter(request, "messageRequest", messageRequest);
return this.Execute(request, cancellationToken);
}and definition of SendMessageRequest should be:
public class SendMessageRequest
{
[JsonProperty("toUserId")]
public string ToUserId { get; set; }
[JsonProperty("content")]
public string Content { get; set; }
}Code sending a message should be:
var result = await client.Send(new SendMessageRequest{ ToUserId="1234", Content="Hello" });
if (result.IsSuccess)
{
// do something
}
else
logger.Error(result.ErrorMessage);Imagine an use case with cars store API.
A generated method for requesting brands list should be:
public Task<Result<IEnumerable<Manufacturer>>> Brands(CancellationToken cancellationToken = default(CancellationToken))
{
var request = new HttpRequestMessage(HttpMethod.Get, "/api/Cars/brands");
return this.Execute<IEnumerable<Manufacturer>>(request, cancellationToken);
}As you can see, this version of Result is a generic type.
Getting manufacturers should be like :
var result = await client.Brands();
if (result.IsSuccess)
{
var manufacturers = result.Value.
// do something
}
else
logger.Error(result.ErrorMessage);Or
var result = await client.Brands();
result
.OnSuccess(manufacturers =>
{
// do something
return Task.CompletedTask;
})
.OnError(error =>
{
logger.Error(error);
return Task.CompletedTask;
});Imagine an API crawling flow as:
- Calling brands.
- Requesting car models for each brands (if previous call succeeded).
- Gettings cars offers for each models (if previous call succeeded).
Code should be like:
var result = await client.Brands()
.ThenMany(b => client.GetBrandModels(b.Id)) // not executed if brands failed
.ThenMany(m => client.GetOffers(m.Manufacturer.Id, m.Id)); // not executed if models failed.
if (result.IsSuccess)
var allOffers = result.Value;Some APIs can return differents schemas for differents status codes.
Imagine a route with possible success responses:
- 200:
string - 204: empty (represented by
Nothing) - 206:
StrangeDto1
Client's method will return a discriminated union.
Code using this route should be:
Result<DiscriminatedUnion<string, Nothing, StrangeDto1>> result = await client.Get(15);
if (!result.IsSuccess)
{
logger.Error(error);
return;
}
string value = result.Value
.Match(
s => s.ToUpper(),//executed if status code id 200
nothing => "", //executed if status code id 204
dto1 => dto1.Message //executed if status code id 206
);
// ...