Skip to content

Commit db11ac1

Browse files
committed
Melhorias de Documentação e Funcionalidade
* Atualizações de Versão e Documentação - Atualizado `SPPreview` para `-preview-4.8` em `Directory.Build.props`. - Documentação XML detalhada adicionada à classe `ResponseTypeMetadata`. * Melhorias em Atributos e Métodos - Adicionado `using System.Reflection;` em `ProduceProblemsAttribute.cs`. - Estendido `ProduceProblemsAttribute` com novas propriedades e construtores. - Modificado `GetStatusCodes` para incluir lógica de tipos relacionados. * Reorganização e Refatoração de Código - Reorganizados `using` em `HttpResultExtensions.cs` e adicionados novos métodos. - Refatorados `FindResult<TEntity>` e `FindResult<TEntity, TId>` para consistência de nomes. * Atualizações de Arquivos e Funcionalidade de Rota - Atualizado `icon2.png` e referência em `libs.targets`. - Introduzida classe `RouteExtensions` para metadados automáticos em rotas.
1 parent d0028ad commit db11ac1

File tree

8 files changed

+268
-57
lines changed

8 files changed

+268
-57
lines changed

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111
<PropertyGroup>
1212
<SPVer>1.0.0</SPVer>
13-
<SPPreview>-preview-4.7</SPPreview>
13+
<SPPreview>-preview-4.8</SPPreview>
1414
</PropertyGroup>
1515
<PropertyGroup>
1616
<FluentValidationVer>12.0.0</FluentValidationVer>

src/RoyalCode.SmartProblems.ApiResults/Metadata/ResponseTypeMetadata.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,69 @@
33

44
namespace RoyalCode.SmartProblems.Metadata;
55

6-
internal sealed class ResponseTypeMetadata : IProducesResponseTypeMetadata
6+
/// <summary>
7+
/// Provides response metadata for an endpoint, including the CLR response <see cref="Type"/>,
8+
/// HTTP <see cref="StatusCode"/>, and the allowed <see cref="ContentTypes"/>.
9+
/// </summary>
10+
/// <remarks>
11+
/// This type is intended to be added to endpoint metadata (e.g. via <c>WithMetadata</c>) so that
12+
/// tools (OpenAPI/Swagger, clients, analyzers) can infer the produced response shapes.
13+
/// When no content types are specified, <c>application/json</c> is assumed by default.
14+
/// </remarks>
15+
/// <example>
16+
/// Adding explicit metadata to an endpoint:
17+
/// <code>
18+
/// app.MapGet("/items/{id:int}", (int id) => Results.Ok(new ItemDto(id)))
19+
/// .WithMetadata(new ResponseTypeMetadata(typeof(ItemDto), 200));
20+
///
21+
/// app.MapPost("/items", (CreateItemDto dto) => Results.Created($"/items/1", null))
22+
/// .WithMetadata(new ResponseTypeMetadata(201));
23+
/// </code>
24+
/// </example>
25+
public sealed class ResponseTypeMetadata : IProducesResponseTypeMetadata
726
{
27+
/// <summary>
28+
/// Initializes a new instance specifying the response <paramref name="type"/>,
29+
/// HTTP <paramref name="statusCode"/>, and optional <paramref name="contentTypes"/>.
30+
/// </summary>
31+
/// <param name="type">The CLR type returned in the response body (nullable for empty responses).</param>
32+
/// <param name="statusCode">The HTTP status code produced by the endpoint.</param>
33+
/// <param name="contentTypes">
34+
/// The content types the endpoint can produce. Defaults to <c>application/json</c> when omitted.
35+
/// </param>
836
public ResponseTypeMetadata(Type? type, int statusCode, params string[]? contentTypes)
937
{
1038
Type = type;
1139
StatusCode = statusCode;
1240
ContentTypes = contentTypes ?? [MediaTypeNames.Application.Json];
1341
}
42+
43+
/// <summary>
44+
/// Initializes a new instance specifying only the HTTP <paramref name="statusCode"/> and optional
45+
/// <paramref name="contentTypes"/> for responses where the body type is not declared or is empty.
46+
/// </summary>
47+
/// <param name="statusCode">The HTTP status code produced by the endpoint.</param>
48+
/// <param name="contentTypes">
49+
/// The content types the endpoint can produce. Defaults to <c>application/json</c> when omitted.
50+
/// </param>
1451
public ResponseTypeMetadata(int statusCode, params string[]? contentTypes)
1552
{
1653
StatusCode = statusCode;
1754
ContentTypes = contentTypes ?? [MediaTypeNames.Application.Json];
1855
}
56+
57+
/// <summary>
58+
/// Gets the CLR type returned in the response body (if any).
59+
/// </summary>
1960
public Type? Type { get; }
61+
62+
/// <summary>
63+
/// Gets the HTTP status code produced by the endpoint.
64+
/// </summary>
2065
public int StatusCode { get; }
66+
67+
/// <summary>
68+
/// Gets the sequence of content types the endpoint can produce.
69+
/// </summary>
2170
public IEnumerable<string> ContentTypes { get; }
2271
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using RoyalCode.SmartProblems;
3+
using RoyalCode.SmartProblems.Metadata;
4+
using System.Reflection;
5+
6+
namespace Microsoft.AspNetCore.Builder;
7+
8+
/// <summary>
9+
/// Provides extension methods for <see cref="RouteHandlerBuilder"/> to populate endpoint metadata
10+
/// based on custom attributes, such as <see cref="ProduceProblemsAttribute"/>.
11+
/// </summary>
12+
public static class RouteExtensions
13+
{
14+
/// <summary>
15+
/// <para>
16+
/// Populates the endpoint metadata for the specified <see cref="RouteHandlerBuilder"/> using the metadata
17+
/// defined on the type parameter <typeparamref name="T"/>.
18+
/// </para>
19+
/// <para>
20+
/// This is typically used to add response metadata for problem details based
21+
/// on the <see cref="ProduceProblemsAttribute"/> applied to <typeparamref name="T"/>.
22+
/// </para>
23+
/// </summary>
24+
/// <typeparam name="T">The type to inspect for metadata attributes.</typeparam>
25+
/// <param name="builder">The route handler builder to populate metadata for.</param>
26+
/// <returns>The same <see cref="RouteHandlerBuilder"/> instance for chaining.</returns>
27+
public static RouteHandlerBuilder PopulateMetadata<T>(this RouteHandlerBuilder builder)
28+
=> PopulateMetadata(builder, typeof(T));
29+
30+
/// <summary>
31+
/// <para>
32+
/// Populates the endpoint metadata for the specified <see cref="RouteHandlerBuilder"/> using the metadata
33+
/// defined on the provided <paramref name="type"/>.
34+
/// </para>
35+
/// <para>
36+
/// If the type is decorated with <see cref="ProduceProblemsAttribute"/>,
37+
/// it will add <see cref="ResponseTypeMetadata"/> for each status code specified in the attribute.
38+
/// </para>
39+
/// </summary>
40+
/// <param name="builder">The route handler builder to populate metadata for.</param>
41+
/// <param name="type">The type to inspect for metadata attributes.</param>
42+
/// <returns>The same <see cref="RouteHandlerBuilder"/> instance for chaining.</returns>
43+
public static RouteHandlerBuilder PopulateMetadata(this RouteHandlerBuilder builder, Type type)
44+
{
45+
var attr = type.GetCustomAttribute<ProduceProblemsAttribute>();
46+
if (attr is not null)
47+
{
48+
Type responseType = typeof(ProblemDetails);
49+
string[] content = ["application/problem+json"];
50+
51+
foreach (var statusCode in attr.GetStatusCodes())
52+
{
53+
builder.WithMetadata(new ResponseTypeMetadata(responseType, statusCode, content));
54+
}
55+
}
56+
return builder;
57+
}
58+
}
59+

src/RoyalCode.SmartProblems.ApiResults/ProduceProblemsAttribute.cs

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
using System.Reflection;
3+
24
namespace RoyalCode.SmartProblems;
35

46
/// <summary>
@@ -17,6 +19,11 @@ public sealed class ProduceProblemsAttribute : Attribute
1719
/// </summary>
1820
public ProblemCategory[]? Categories { get; set; }
1921

22+
/// <summary>
23+
/// A related type that can be used to provide additional context for the problems produced by the method.
24+
/// </summary>
25+
public Type? RelatedType { get; set; }
26+
2027
/// <summary>
2128
/// Define the categories of problems that the method produces for problems results.
2229
/// </summary>
@@ -35,6 +42,15 @@ public ProduceProblemsAttribute(params int[] statusCodes)
3542
StatusCodes = statusCodes;
3643
}
3744

45+
/// <summary>
46+
/// Define the related type that can be used to provide additional context for the problems produced by the method.
47+
/// </summary>
48+
/// <param name="relatedType">The related type that can be used to provide additional context for the problems produced by the method.</param>
49+
public ProduceProblemsAttribute(Type relatedType)
50+
{
51+
RelatedType = relatedType;
52+
}
53+
3854
/// <summary>
3955
/// Define the status codes and categories of problems that the method produces for problems results.
4056
/// </summary>
@@ -46,30 +62,63 @@ public ProduceProblemsAttribute(int[] statusCodes, ProblemCategory[] categories)
4662
Categories = categories;
4763
}
4864

65+
/// <summary>
66+
/// Define the status codes and categories of problems that the method produces for problems results.
67+
/// </summary>
68+
/// <param name="relatedType">The related type that can be used to provide additional context for the problems produced by the method.</param>
69+
/// <param name="categories">The problem categories that the method produces for problems results.</param>
70+
public ProduceProblemsAttribute(Type relatedType, ProblemCategory[] categories)
71+
{
72+
RelatedType = relatedType;
73+
Categories = categories;
74+
}
75+
76+
/// <summary>
77+
/// Define the status codes and categories of problems that the method produces for problems results.
78+
/// </summary>
79+
/// <param name="relatedType">The related type that can be used to provide additional context for the problems produced by the method.</param>
80+
/// <param name="statusCodes">The status codes that the method produces for problems results.</param>
81+
/// <param name="categories">The problem categories that the method produces for problems results.</param>
82+
public ProduceProblemsAttribute(Type relatedType, int[] statusCodes, ProblemCategory[] categories)
83+
{
84+
RelatedType = relatedType;
85+
StatusCodes = statusCodes;
86+
Categories = categories;
87+
}
88+
4989
/// <summary>
5090
/// Creates a new instance of <see cref="ProduceProblemsAttribute"/>.
5191
/// </summary>
5292
public ProduceProblemsAttribute() { }
5393

54-
internal IEnumerable<int> GetStatusCodes()
94+
/// <summary>
95+
/// Obtains the status codes that this attribute produces for problems results.
96+
/// </summary>
97+
/// <returns>
98+
/// An <see cref="IEnumerable{Int32}"/> containing the status codes that this attribute produces for problems results.
99+
/// </returns>
100+
public IEnumerable<int> GetStatusCodes()
55101
{
56102
if (StatusCodes is not null)
57-
foreach (var statusCode in StatusCodes)
58-
yield return statusCode;
103+
for (var i = 0; i < StatusCodes.Length; i++)
104+
yield return StatusCodes[i];
105+
106+
if (Categories is not null)
107+
for (var i = 0; i < Categories.Length; i++)
108+
yield return Categories[i] switch
109+
{
110+
ProblemCategory.NotFound => 404,
111+
ProblemCategory.InvalidParameter => 400,
112+
ProblemCategory.ValidationFailed => 422,
113+
ProblemCategory.CustomProblem => 422,
114+
ProblemCategory.InvalidState => 409,
115+
ProblemCategory.InternalServerError => 500,
116+
_ => 400
117+
};
59118

60-
if (Categories is null)
61-
yield break;
62-
63-
foreach (var category in Categories)
64-
yield return category switch
65-
{
66-
ProblemCategory.NotFound => 404,
67-
ProblemCategory.InvalidParameter => 400,
68-
ProblemCategory.ValidationFailed => 422,
69-
ProblemCategory.InvalidState => 409,
70-
ProblemCategory.NotAllowed => 403,
71-
ProblemCategory.InternalServerError => 500,
72-
_ => 400
73-
};
119+
var attr = RelatedType?.GetCustomAttribute<ProduceProblemsAttribute>();
120+
if (attr != null)
121+
foreach (var code in attr.GetStatusCodes())
122+
yield return code;
74123
}
75124
}

src/RoyalCode.SmartProblems.Http/HttpResultExtensions.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
using RoyalCode.SmartProblems;
22
using RoyalCode.SmartProblems.Conversions;
3+
using RoyalCode.SmartProblems.Http;
34
using System.Net.Http.Json;
45
using System.Runtime.CompilerServices;
56
using System.Text.Json;
6-
using RoyalCode.SmartProblems.Http;
7+
using System.Text.Json.Serialization.Metadata;
78

89
// ReSharper disable CheckNamespace
910

@@ -73,6 +74,26 @@ public static Task<Result<TValue>> ToResultAsync<TValue>(
7374
return ToResultAsync<TValue>(response, null, options, token);
7475
}
7576

77+
/// <summary>
78+
/// <para>
79+
/// Get <see cref="Result{TValue}" /> from <see cref="HttpResponseMessage"/>.
80+
/// </para>
81+
/// </summary>
82+
/// <typeparam name="TValue">The type of the value.</typeparam>
83+
/// <param name="response">The <see cref="HttpResponseMessage"/>.</param>
84+
/// <param name="jsonTypeInfo">
85+
/// The <see cref="JsonTypeInfo{T}"/> for the <typeparamref name="TValue"/>,
86+
/// used when status code is success.
87+
/// </param>
88+
/// <param name="ct">The <see cref="CancellationToken"/>.</param>
89+
/// <returns>The <see cref="Result{TValue}"/>.</returns>
90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
public static Task<Result<TValue>> ToResultAsync<TValue>(
92+
this HttpResponseMessage response, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken ct = default)
93+
{
94+
return ToResultAsync(response, null, jsonTypeInfo, ct);
95+
}
96+
7697
/// <summary>
7798
/// <para>
7899
/// Get <see cref="Result{TValue}" /> from <see cref="HttpResponseMessage"/>.
@@ -106,6 +127,39 @@ public static async Task<Result<TValue>> ToResultAsync<TValue>(
106127
return value!;
107128
}
108129

130+
/// <summary>
131+
/// <para>
132+
/// Get <see cref="Result{TValue}" /> from <see cref="HttpResponseMessage"/>.
133+
/// </para>
134+
/// </summary>
135+
/// <typeparam name="TValue">The type of the value.</typeparam>
136+
/// <param name="response">The <see cref="HttpResponseMessage"/>.</param>
137+
/// <param name="failureTypeReader">
138+
/// A <see cref="FailureTypeReader"/> to read the error content when the status code is not success
139+
/// and the content is not a problem details.
140+
/// </param>
141+
/// <param name="jsonTypeInfo">
142+
/// The <see cref="JsonTypeInfo{T}"/> for the <typeparamref name="TValue"/>,
143+
/// used when status code is success.
144+
/// </param>
145+
/// <param name="ct">The <see cref="CancellationToken"/>.</param>
146+
/// <returns>The <see cref="Result{TValue}"/>.</returns>
147+
public static async Task<Result<TValue>> ToResultAsync<TValue>(
148+
this HttpResponseMessage response,
149+
FailureTypeReader? failureTypeReader,
150+
JsonTypeInfo<TValue> jsonTypeInfo,
151+
CancellationToken ct = default)
152+
{
153+
// on error, read the content as a problem details or text.
154+
if (!response.IsSuccessStatusCode)
155+
return await response.ReadErrorStatus(failureTypeReader, ct);
156+
157+
// on success, with value, the value must be deserialized
158+
var value = await response.Content.ReadFromJsonAsync(jsonTypeInfo, ct);
159+
160+
return value!;
161+
}
162+
109163
private static Task<Problems> ReadErrorStatus(
110164
this HttpResponseMessage response, FailureTypeReader? failureTypeReader, CancellationToken token)
111165
{

0 commit comments

Comments
 (0)