Skip to content

Commit 989774d

Browse files
committed
3.1.3 - hibrid cache rename and path routes
1 parent be6c565 commit 989774d

17 files changed

Lines changed: 709 additions & 43 deletions

File tree

.github/workflows/build-test-publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
workflow_dispatch:
99

1010
env:
11-
RELEASE_VERSION: v3.1.2
11+
RELEASE_VERSION: v3.1.3
1212

1313
# Add workflow-level permissions
1414
permissions:

NpgsqlRest/Defaults/CommentParsers/HttpHandler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ private static void HandleHttp(
6060
{
6161
endpoint.Path = string.Concat("/", endpoint.Path);
6262
}
63+
// Extract path parameters from the path template
64+
endpoint.PathParameters = ExtractPathParameters(endpoint.Path);
6365
}
6466
}
6567

NpgsqlRest/Defaults/CommentParsers/PathHandler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ private static void HandlePath(
3030
{
3131
endpoint.Path = string.Concat("/", endpoint.Path);
3232
}
33+
// Extract path parameters from the path template
34+
endpoint.PathParameters = ExtractPathParameters(endpoint.Path);
3335

3436
Logger?.CommentSetHttp(description, endpoint.Method, endpoint.Path);
3537
urlDescription = string.Concat(endpoint.Method.ToString(), " ", endpoint.Path);

NpgsqlRest/Defaults/CommentParsers/Utilities.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,38 @@
1+
using System.Text.RegularExpressions;
2+
13
namespace NpgsqlRest.Defaults;
24

35
internal static partial class DefaultCommentParser
46
{
7+
// Regex to match path parameters like {param_name} or {paramName}
8+
[GeneratedRegex(@"\{(\w+)\}", RegexOptions.Compiled)]
9+
private static partial Regex PathParameterRegex();
10+
11+
/// <summary>
12+
/// Extracts parameter names from a path template.
13+
/// For example: "/products/{p_id}/reviews/{review_id}" returns ["p_id", "review_id"]
14+
/// </summary>
15+
public static string[]? ExtractPathParameters(string path)
16+
{
17+
if (string.IsNullOrEmpty(path) || !path.Contains('{'))
18+
{
19+
return null;
20+
}
21+
22+
var matches = PathParameterRegex().Matches(path);
23+
if (matches.Count == 0)
24+
{
25+
return null;
26+
}
27+
28+
var result = new string[matches.Count];
29+
for (int i = 0; i < matches.Count; i++)
30+
{
31+
result[i] = matches[i].Groups[1].Value;
32+
}
33+
return result;
34+
}
35+
536
public static bool StrEquals(string str1, string str2) =>
637
str1.Equals(str2, StringComparison.OrdinalIgnoreCase);
738

NpgsqlRest/Enums.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ public enum RoutineType { Table, View, Function, Procedure, Other }
44
public enum CrudType { Select, Insert, Update, Delete }
55
public enum Method { GET, PUT, POST, DELETE }
66
public enum RequestParamType { QueryString, BodyJson }
7-
public enum ParamType { QueryString, BodyJson, BodyParam, HeaderParam }
7+
public enum ParamType { QueryString, BodyJson, BodyParam, HeaderParam, PathParam }
88
public enum CommentHeader { None, Simple, Full }
99
public enum TextResponseNullHandling { EmptyString, NullLiteral, NoContent }
1010
public enum QueryStringNullHandling { EmptyString, NullLiteral, Ignore }

NpgsqlRest/NpgsqlRest.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
<GenerateDocumentationFile>true</GenerateDocumentationFile>
3030
<PackageReadmeFile>README.MD</PackageReadmeFile>
3131
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
32-
<Version>3.1.2</Version>
33-
<AssemblyVersion>3.1.2</AssemblyVersion>
34-
<FileVersion>3.1.2</FileVersion>
35-
<PackageVersion>3.1.2</PackageVersion>
32+
<Version>3.1.3</Version>
33+
<AssemblyVersion>3.1.3</AssemblyVersion>
34+
<FileVersion>3.1.3</FileVersion>
35+
<PackageVersion>3.1.3</PackageVersion>
3636
<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>
3737
</PropertyGroup>
3838

NpgsqlRest/NpgsqlRestEndpoint.cs

Lines changed: 219 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,113 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
552552
}
553553
}
554554

555+
// path parameter - extract from RouteValues
556+
if (parameter.Value is null && endpoint.HasPathParameters)
557+
{
558+
string? matchedPathParam = null;
559+
foreach (var pathParam in endpoint.PathParameters!)
560+
{
561+
if (string.Equals(pathParam, parameter.ConvertedName, StringComparison.OrdinalIgnoreCase) ||
562+
string.Equals(pathParam, parameter.ActualName, StringComparison.OrdinalIgnoreCase))
563+
{
564+
matchedPathParam = pathParam;
565+
break;
566+
}
567+
}
568+
569+
// Try to get route value using the path parameter name from the template
570+
if (matchedPathParam is not null && context.Request.RouteValues.TryGetValue(matchedPathParam, out var routeValue))
571+
{
572+
StringValues pathStringValues = routeValue?.ToString() ?? "";
573+
if (TryParseParameter(parameter, ref pathStringValues, endpoint.QueryStringNullHandling))
574+
{
575+
parameter.ParamType = ParamType.PathParam;
576+
parameter.QueryStringValues = pathStringValues;
577+
578+
if (Options.ValidateParameters is not null)
579+
{
580+
Options.ValidateParameters(new ParameterValidationValues(
581+
context,
582+
routine,
583+
parameter));
584+
if (context.Response.HasStarted || context.Response.StatusCode != (int)HttpStatusCode.OK)
585+
{
586+
return;
587+
}
588+
}
589+
if (Options.ValidateParametersAsync is not null)
590+
{
591+
await Options.ValidateParametersAsync(new ParameterValidationValues(
592+
context,
593+
routine,
594+
parameter));
595+
if (context.Response.HasStarted || context.Response.StatusCode != (int)HttpStatusCode.OK)
596+
{
597+
return;
598+
}
599+
}
600+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
601+
{
602+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
603+
{
604+
cacheKeys?.Append(NpgsqlRestParameter.GetCacheKeySeparator());
605+
cacheKeys?.Append(parameter.GetCacheStringValue());
606+
}
607+
}
608+
609+
if (Options.HttpClientOptions.Enabled)
610+
{
611+
if (parameter.TypeDescriptor.CustomType is not null)
612+
{
613+
if (HttpClientTypes.Definitions.ContainsKey(parameter.TypeDescriptor.CustomType))
614+
{
615+
customHttpTypes.Add(parameter.TypeDescriptor.CustomType);
616+
}
617+
}
618+
}
619+
command.Parameters.Add(parameter);
620+
621+
if (hasNulls is false && parameter.Value == DBNull.Value)
622+
{
623+
hasNulls = true;
624+
}
625+
626+
if (formatter.IsFormattable is false)
627+
{
628+
if (formatter.RefContext)
629+
{
630+
commandText = string.Concat(commandText,
631+
formatter.AppendCommandParameter(parameter, paramIndex, context));
632+
if (context.Response.HasStarted || context.Response.StatusCode != (int)HttpStatusCode.OK)
633+
{
634+
return;
635+
}
636+
}
637+
else
638+
{
639+
commandText = string.Concat(commandText,
640+
formatter.AppendCommandParameter(parameter, paramIndex));
641+
}
642+
}
643+
paramIndex++;
644+
if (shouldLog && Options.LogCommandParameters)
645+
{
646+
var p = Options.AuthenticationOptions.ObfuscateAuthParameterLogValues && endpoint.IsAuth ?
647+
"***" :
648+
FormatParameterForLog(parameter);
649+
cmdLog!.AppendLine(string.Concat(
650+
"-- $",
651+
paramIndex.ToString(),
652+
" ", parameter.TypeDescriptor.OriginalType,
653+
" = ",
654+
p));
655+
}
656+
657+
continue;
658+
}
659+
}
660+
}
661+
555662
if (queryDict.TryGetValue(parameter.ConvertedName, out var qsValue) is false)
556663
{
557664
if (parameter.Value is null)
@@ -719,9 +826,12 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
719826
return;
720827
}
721828

722-
if (bodyDict.Count != routine.ParamCount && overloads.Count > 0)
829+
// Account for path parameters when counting body parameters
830+
var pathParamCount = endpoint.PathParameters?.Length ?? 0;
831+
var expectedBodyParamCount = routine.ParamCount - pathParamCount;
832+
if (bodyDict.Count != expectedBodyParamCount && overloads.Count > 0)
723833
{
724-
if (overloads.TryGetValue(string.Concat(entry.Key, bodyDict.Count), out var overload))
834+
if (overloads.TryGetValue(string.Concat(entry.Key, bodyDict.Count + pathParamCount), out var overload))
725835
{
726836
routine = overload.Endpoint.Routine;
727837
endpoint = overload.Endpoint;
@@ -872,6 +982,113 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
872982
}
873983
}
874984

985+
// path parameter - extract from RouteValues (for JSON body mode)
986+
if (parameter.Value is null && endpoint.HasPathParameters)
987+
{
988+
string? matchedPathParam = null;
989+
foreach (var pathParam in endpoint.PathParameters!)
990+
{
991+
if (string.Equals(pathParam, parameter.ConvertedName, StringComparison.OrdinalIgnoreCase) ||
992+
string.Equals(pathParam, parameter.ActualName, StringComparison.OrdinalIgnoreCase))
993+
{
994+
matchedPathParam = pathParam;
995+
break;
996+
}
997+
}
998+
999+
// Try to get route value using the path parameter name from the template
1000+
if (matchedPathParam is not null && context.Request.RouteValues.TryGetValue(matchedPathParam, out var routeValue))
1001+
{
1002+
StringValues pathStringValues = routeValue?.ToString() ?? "";
1003+
if (TryParseParameter(parameter, ref pathStringValues, endpoint.QueryStringNullHandling))
1004+
{
1005+
parameter.ParamType = ParamType.PathParam;
1006+
parameter.QueryStringValues = pathStringValues;
1007+
1008+
if (Options.ValidateParameters is not null)
1009+
{
1010+
Options.ValidateParameters(new ParameterValidationValues(
1011+
context,
1012+
routine,
1013+
parameter));
1014+
if (context.Response.HasStarted || context.Response.StatusCode != (int)HttpStatusCode.OK)
1015+
{
1016+
return;
1017+
}
1018+
}
1019+
if (Options.ValidateParametersAsync is not null)
1020+
{
1021+
await Options.ValidateParametersAsync(new ParameterValidationValues(
1022+
context,
1023+
routine,
1024+
parameter));
1025+
if (context.Response.HasStarted || context.Response.StatusCode != (int)HttpStatusCode.OK)
1026+
{
1027+
return;
1028+
}
1029+
}
1030+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
1031+
{
1032+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
1033+
{
1034+
cacheKeys?.Append(NpgsqlRestParameter.GetCacheKeySeparator());
1035+
cacheKeys?.Append(parameter.GetCacheStringValue());
1036+
}
1037+
}
1038+
1039+
if (Options.HttpClientOptions.Enabled)
1040+
{
1041+
if (parameter.TypeDescriptor.CustomType is not null)
1042+
{
1043+
if (HttpClientTypes.Definitions.ContainsKey(parameter.TypeDescriptor.CustomType))
1044+
{
1045+
customHttpTypes.Add(parameter.TypeDescriptor.CustomType);
1046+
}
1047+
}
1048+
}
1049+
command.Parameters.Add(parameter);
1050+
1051+
if (hasNulls is false && parameter.Value == DBNull.Value)
1052+
{
1053+
hasNulls = true;
1054+
}
1055+
1056+
if (formatter.IsFormattable is false)
1057+
{
1058+
if (formatter.RefContext)
1059+
{
1060+
commandText = string.Concat(commandText,
1061+
formatter.AppendCommandParameter(parameter, paramIndex, context));
1062+
if (context.Response.HasStarted || context.Response.StatusCode != (int)HttpStatusCode.OK)
1063+
{
1064+
return;
1065+
}
1066+
}
1067+
else
1068+
{
1069+
commandText = string.Concat(commandText,
1070+
formatter.AppendCommandParameter(parameter, paramIndex));
1071+
}
1072+
}
1073+
paramIndex++;
1074+
if (shouldLog && Options.LogCommandParameters)
1075+
{
1076+
var p = Options.AuthenticationOptions.ObfuscateAuthParameterLogValues && endpoint.IsAuth ?
1077+
"***" :
1078+
FormatParameterForLog(parameter);
1079+
cmdLog!.AppendLine(string.Concat(
1080+
"-- $",
1081+
paramIndex.ToString(),
1082+
" ", parameter.TypeDescriptor.OriginalType,
1083+
" = ",
1084+
p));
1085+
}
1086+
1087+
continue;
1088+
}
1089+
}
1090+
}
1091+
8751092
if (bodyDict.TryGetValue(parameter.ConvertedName, out var value) is false)
8761093
{
8771094
if (parameter.Value is null)

NpgsqlRest/RoutineEndpoint.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,16 @@ public string? BodyParameterName
9898
/// Instead of executing the routine, it removes the cached entry for the given parameters.
9999
/// </summary>
100100
public bool InvalidateCache { get; set; } = false;
101+
102+
/// <summary>
103+
/// List of parameter names that are extracted from the URL path.
104+
/// For example, path "/products/{p_id}" would have PathParameters = ["p_id"].
105+
/// These parameters are populated from ASP.NET Core RouteValues.
106+
/// </summary>
107+
public string[]? PathParameters { get; set; } = null;
108+
109+
/// <summary>
110+
/// Returns true if this endpoint has any path parameters defined.
111+
/// </summary>
112+
public bool HasPathParameters => PathParameters is not null && PathParameters.Length > 0;
101113
}

NpgsqlRestClient/Builder.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,7 @@ public CacheType ConfigureCacheServices()
10301030
if (type == CacheType.Hybrid)
10311031
{
10321032
var redisConfiguration = _config.GetConfigStr("RedisConfiguration", cacheCfg);
1033-
var useRedisBackend = _config.GetConfigBool("UseRedisBackend", cacheCfg, false);
1033+
var useRedisBackend = _config.GetConfigBool("HybridCacheUseRedisBackend", cacheCfg, false);
10341034

10351035
// Only register Redis as L2 cache if explicitly enabled
10361036
if (useRedisBackend && !string.IsNullOrEmpty(redisConfiguration))
@@ -1049,11 +1049,11 @@ public CacheType ConfigureCacheServices()
10491049
// Register HybridCache with configuration
10501050
Instance.Services.AddHybridCache(options =>
10511051
{
1052-
options.MaximumKeyLength = _config.GetConfigInt("MaximumKeyLength", cacheCfg) ?? 1024;
1053-
options.MaximumPayloadBytes = _config.GetConfigInt("MaximumPayloadBytes", cacheCfg) ?? 1024 * 1024;
1052+
options.MaximumKeyLength = _config.GetConfigInt("HybridCacheMaximumKeyLength", cacheCfg) ?? 1024;
1053+
options.MaximumPayloadBytes = _config.GetConfigInt("HybridCacheMaximumPayloadBytes", cacheCfg) ?? 1024 * 1024;
10541054

1055-
var defaultExpiration = Parser.ParsePostgresInterval(_config.GetConfigStr("DefaultExpiration", cacheCfg));
1056-
var localExpiration = Parser.ParsePostgresInterval(_config.GetConfigStr("LocalCacheExpiration", cacheCfg));
1055+
var defaultExpiration = Parser.ParsePostgresInterval(_config.GetConfigStr("HybridCacheDefaultExpiration", cacheCfg));
1056+
var localExpiration = Parser.ParsePostgresInterval(_config.GetConfigStr("HybridCacheLocalCacheExpiration", cacheCfg));
10571057

10581058
options.DefaultEntryOptions = new HybridCacheEntryOptions
10591059
{

NpgsqlRestClient/NpgsqlRestClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<PublishAot>true</PublishAot>
1111
<PublishTrimmed>true</PublishTrimmed>
1212
<TrimMode>full</TrimMode>
13-
<Version>3.1.2</Version>
13+
<Version>3.1.3</Version>
1414
</PropertyGroup>
1515

1616
<ItemGroup>

0 commit comments

Comments
 (0)