23 Commits

Author SHA1 Message Date
d6af24cd91 Remove PlaceholderResolutionException class
Deleted the PlaceholderResolutionException class and its namespace. This exception was previously used for unresolved placeholders due to missing properties. All related properties and custom messages have been removed.
2026-03-30 14:33:23 +02:00
bb5eac023c Change ReplacePlaceholders to return NULL for unresolved
Previously, ReplacePlaceholders threw PlaceholderResolutionException when a placeholder could not be resolved. Now, unresolved placeholders are replaced with "NULL" instead. All exception references and related tests have been updated to reflect this new behavior. Documentation has also been revised accordingly.
2026-03-30 14:33:13 +02:00
77baf395ce Update ReplacePlaceholders mapping to use single argument
Removed src.Profile from ReplacePlaceholders calls in DtoMappingProfile for PreprocessingQuery and PostprocessingQuery mappings, now passing only src as the argument. This simplifies the mapping logic and aligns with the updated method signature.
2026-03-30 14:32:31 +02:00
6c9eab6df6 Refactor ResultViewDto status and add info/error fields
Replaced StatusCode with a RecStatus Status property in ResultViewDto. Added Info and Error string properties to provide additional result details. Imported ReC.Domain.Constants for RecStatus usage.
2026-03-30 13:21:48 +02:00
c64794755d Remove "EXEC" from stored procedure call in builder
Standardize StoredProcedureBuilder usage by omitting the "EXEC" keyword when specifying the procedure name. This ensures consistent and correct invocation of stored procedures.
2026-03-30 13:21:11 +02:00
de2185bf0a Specify byte as underlying type for ResultType enum
Changed ResultType enum declaration to explicitly use byte as its underlying type for improved memory efficiency and clarity.
2026-03-30 13:20:40 +02:00
fde9735b27 Remove redundant Result property validation in InsertObject
Removed the rule requiring Result to have StatusId, Info, or Error set. Now only ResultActionId is required when Result is present.
2026-03-30 13:20:21 +02:00
0342b9e0c6 Unify error status code as RecStatus.Failed
Renamed RecStatus.QueryFailed to RecStatus.Failed and updated all usages and documentation to reflect its broader purpose as a general failure code for any operation, not just SQL queries. Improved consistency in error handling and status reporting across the codebase.
2026-03-30 11:55:58 +02:00
47698b9046 Rename ToStatus to ToRecStatus for HttpStatusCode conversion
Standardize extension method naming by renaming ToStatus to ToRecStatus for converting HttpStatusCode to RecStatus across the codebase. Updated all usages and related tests for consistency and clarity.
2026-03-30 11:40:26 +02:00
a03d21ebc6 Rename StatusExtensions to RecStatusExtensions for clarity
Renamed the StatusExtensions class and related XML documentation references to RecStatusExtensions to better reflect its association with the RecStatus enum and improve code clarity and consistency.
2026-03-30 11:39:37 +02:00
acff0aca89 Rename Status enum to RecStatus across the codebase
Refactored all usages of the Status enum to RecStatus to improve clarity and prevent naming conflicts with other status enums (e.g., HTTP status codes). Updated command handlers, behaviors, data models, and extension methods to use RecStatus, and adjusted related serialization logic accordingly. This makes the domain-specific status handling more explicit and maintainable.
2026-03-30 11:35:22 +02:00
ce0e53baf6 Update test for InsertResultCommand Status property change
Refactored InsertResultProcedure_runs_via_mediator to use the new Status property (set via HttpStatusCode.OK.ToStatus()) instead of the old StatusId integer. Added necessary using directives to support updated types and methods.
2026-03-30 11:33:42 +02:00
620c0eff22 Add ToStatus extension for HttpStatusCode to Status conversion
Introduced a ToStatus extension method in StatusExtensions to enable direct conversion from HttpStatusCode to Status by casting. This simplifies mapping between HTTP status codes and internal Status values.
2026-03-30 11:33:28 +02:00
68f4486fa1 Make Status required in InsertResultCommand
Changed the Status property in InsertResultCommand from nullable to required, ensuring that a Status value must always be provided when creating an instance. This improves data integrity and prevents accidental omission of the Status field.
2026-03-30 11:33:08 +02:00
2b5e63cb45 Update result status handling in InsertResultCommand
Replaced StatusId with Status property in InsertResultCommand, mapping HTTP status codes to application-specific status enums using ToStatus(). Exception handling now sets Status to QueryFailed. Removed usage of StatusId.
2026-03-30 11:22:57 +02:00
e9e697fa0d Update to use Result.Status instead of StatusId in insert proc
Changed the parameter for "pRESULT_STATUS_ID" from request.Result?.StatusId to request.Result?.Status in InsertObjectProcedureHandler. This aligns with updates to the data model or business logic, ensuring the correct status property is used when inserting objects.
2026-03-30 11:22:27 +02:00
606eccb855 Add Status to InsertResultCommand for query outcome reporting
Enhance PostprocessingBehavior and PreprocessingBehavior to set the Status property on InsertResultCommand. Status is now set to QuerySuccess on successful execution and QueryFailed on exceptions, improving clarity of query execution results.
2026-03-30 11:11:51 +02:00
3146acfa45 Refactor InsertResultCommand to use Status enum
Replaced StatusId (short?) with Status (Status?) in InsertResultCommand, moving from a numeric status identifier to a more descriptive or structured status representation from ReC.Domain.Constants.
2026-03-30 11:11:28 +02:00
f363872e7a Refactor ResultView: replace StatusCode with Status object
Replaced the short? StatusCode property in ResultView with a Status object to provide a more descriptive representation of status information. This change improves code clarity and supports richer status handling.
2026-03-30 10:51:58 +02:00
ed4683323d Change Status enum underlying type to short
Explicitly set the Status enum's underlying type to short instead of the default int to optimize memory usage and clarify intent. No other changes were made.
2026-03-30 10:50:33 +02:00
4aeef10ef7 Add StatusExtensions with HTTP status mapping methods
Added StatusExtensions.cs with extension methods for Status and HttpStatusCode:
- ToHttpStatusCode maps Status to nullable HttpStatusCode if possible.
- IsSuccess checks if a Status or HttpStatusCode represents a successful response.
- Handles both direct Status values and those convertible to HTTP codes.
2026-03-30 10:41:39 +02:00
e04e90d8c6 Add Status enum for HTTP and SQL operation status codes
Introduced Status enum in ReC.Domain.Constants to unify status reporting for both HTTP responses and SQL query execution results. The enum includes all standard HTTP status codes and custom codes for SQL query success/failure, with detailed documentation for each value. This standardizes status handling across web and database operations.
2026-03-30 10:41:09 +02:00
93b5f976d3 Refactor stored procedure SQL construction and execution
Centralize stored procedure SQL generation in StoredProcedureBuilder,
allowing handlers to specify procedure name and return variable.
Removes manual SQL string building from DeleteObjectProcedure and
UpdateObjectProcedure handlers, reducing boilerplate and improving
maintainability.
2026-03-30 09:30:07 +02:00
19 changed files with 524 additions and 78 deletions

View File

@@ -23,6 +23,7 @@ public class PostprocessingBehavior(IRecDbContext context, ISender sender) : IPi
await sender.Send(new InsertResultCommand()
{
Status = RecStatus.QuerySuccess,
ActionId = request.Action.Id,
Info = info,
Type = ResultType.Post
@@ -35,6 +36,7 @@ public class PostprocessingBehavior(IRecDbContext context, ISender sender) : IPi
await sender.Send(new InsertResultCommand()
{
Status = RecStatus.Failed,
ActionId = request.Action.Id,
Error = error,
Type = ResultType.Post

View File

@@ -20,6 +20,7 @@ public class PreprocessingBehavior(IRecDbContext context, ISender sender) : IPip
await sender.Send(new InsertResultCommand()
{
Status = RecStatus.QuerySuccess,
ActionId = request.Action.Id,
Info = JsonSerializer.Serialize(result),
Type = ResultType.Pre
@@ -30,6 +31,7 @@ public class PreprocessingBehavior(IRecDbContext context, ISender sender) : IPip
{
await sender.Send(new InsertResultCommand()
{
Status = RecStatus.Failed,
ActionId = request.Action.Id,
Error = ex.ToString(),
Type = ResultType.Pre

View File

@@ -8,9 +8,9 @@ public class DtoMappingProfile : AutoMapper.Profile
{
CreateMap<RecActionView, RecActionViewDto>()
.ForMember(dest => dest.PreprocessingQuery, opt => opt.MapFrom((src, _) =>
src.PreprocessingQuery?.ReplacePlaceholders(src, src.Profile)))
src.PreprocessingQuery?.ReplacePlaceholders(src)))
.ForMember(dest => dest.PostprocessingQuery, opt => opt.MapFrom((src, _) =>
src.PostprocessingQuery?.ReplacePlaceholders(src, src.Profile)));
src.PostprocessingQuery?.ReplacePlaceholders(src)));
CreateMap<ResultView, ResultViewDto>();
CreateMap<ProfileView, ProfileViewDto>();

View File

@@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Text.RegularExpressions;
using ReC.Application.Common.Exceptions;
namespace ReC.Application.Common.Dto;
@@ -14,10 +13,8 @@ public static partial class PlaceholderExtensions
/// Replaces placeholders in the format <c>{#ANY_STRING#COLUMN_NAME}</c> with the corresponding
/// property value resolved via <see cref="GetValueByColumnName{T}"/> from the provided objects.
/// Values are converted to SQL-compatible string representations.
/// If a placeholder cannot be resolved, it is replaced with <c>NULL</c>.
/// </summary>
/// <exception cref="PlaceholderResolutionException">
/// Thrown when a placeholder's column name cannot be resolved from any of the provided objects.
/// </exception>
public static string ReplacePlaceholders(this string str, params object?[] objects)
{
return PlaceholderRegex().Replace(str, match =>
@@ -37,7 +34,7 @@ public static partial class PlaceholderExtensions
return ToSqlLiteral(value);
}
throw new PlaceholderResolutionException(placeholder, columnName, str);
return "NULL";
});
}

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Dto;
using ReC.Domain.Constants;
namespace ReC.Application.Common.Dto;
public record ResultViewDto
{
@@ -14,7 +16,7 @@ public record ResultViewDto
public string? ProfileName { get; init; }
public short? StatusCode { get; init; }
public RecStatus Status { get; set; }
public string? StatusName { get; init; }
@@ -22,6 +24,10 @@ public record ResultViewDto
public string? Body { get; init; }
public string? Info { get; set; }
public string? Error { get; set; }
public string? AddedWho { get; init; }
public DateTime? AddedWhen { get; init; }

View File

@@ -1,11 +0,0 @@
namespace ReC.Application.Common.Exceptions;
public class PlaceholderResolutionException(string placeholder, string columnName, string input)
: Exception($"Failed to resolve placeholder '{placeholder}'. No object contains a property with column name '{columnName}'. Input: '{input}'")
{
public string Placeholder { get; } = placeholder;
public string ColumnName { get; } = columnName;
public string Input { get; } = input;
}

View File

@@ -5,7 +5,6 @@ using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Options;
using System.Text;
namespace ReC.Application.Common.Procedures.DeleteProcedure;
@@ -36,21 +35,15 @@ public class DeleteObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
{
public async Task<int> Handle(DeleteObjectProcedure request, CancellationToken cancel)
{
var sp = new StoredProcedureBuilder("EXEC @RC = [dbo].[PRREC_DELETE_OBJECT]")
var sp = new StoredProcedureBuilder("[dbo].[PRREC_DELETE_OBJECT]", "RC")
.Add("pENTITY", request.Entity)
.Add("pSTART", request.Start.ToString())
.Add("pEND", request.End.ToString())
.Add("pFORCE", request.Force);
var sql = new StringBuilder()
.AppendLine("DECLARE @RC SMALLINT = 0;")
.Append(sp.BuildSql()).AppendLine(";")
.AppendLine("SELECT @RC;")
.ToString();
try
{
var result = await repo.ExecuteQueryRawAsync(sql, sp.BuildParameters(), cancel);
var result = await repo.ExecuteQueryRawAsync(sp.BuildSql(), sp.BuildParameters(), cancel);
if (result > 0)
{

View File

@@ -37,7 +37,7 @@ public class InsertObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
{
public async Task<long> Handle(InsertObjectProcedure request, CancellationToken cancel)
{
var sp = new StoredProcedureBuilder("EXEC [dbo].[PRREC_INSERT_OBJECT]")
var sp = new StoredProcedureBuilder("[dbo].[PRREC_INSERT_OBJECT]")
.Add("pENTITY", request.Entity)
.Add("pADDED_WHO", request.AddedWho)
.Add("pADDED_WHEN", DateTime.UtcNow)
@@ -76,7 +76,7 @@ public class InsertObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
.Add("pPROFILE_LOG_LEVEL_ID", request.Profile?.LogLevelId, SqlDbType.TinyInt)
.Add("pPROFILE_LANGUAGE_ID", request.Profile?.LanguageId, SqlDbType.SmallInt)
.Add("pRESULT_ACTION_ID", request.Result?.ActionId)
.Add("pRESULT_STATUS_ID", request.Result?.StatusId, SqlDbType.SmallInt)
.Add("pRESULT_STATUS_ID", request.Result?.Status, SqlDbType.SmallInt)
.Add("pRESULT_TYPE_ID", request.Result?.Type is not null ? (byte)request.Result.Type : null, SqlDbType.TinyInt)
.Add("pRESULT_HEADER", request.Result?.Header)
.Add("pRESULT_BODY", request.Result?.Body)

View File

@@ -4,22 +4,19 @@ using System.Text;
namespace ReC.Application.Common.Procedures;
internal sealed class StoredProcedureBuilder
internal sealed class StoredProcedureBuilder(string procedureName, string? returnVariable = null)
{
private readonly StringBuilder _sql;
private readonly StringBuilder _execSql = returnVariable is not null
? new StringBuilder($"EXEC @{returnVariable} = {procedureName}")
: new StringBuilder($"EXEC {procedureName}");
private readonly List<SqlParameter> _parameters = [];
private char _separator = ' ';
public StoredProcedureBuilder(string execPrefix)
{
_sql = new StringBuilder(execPrefix);
}
public StoredProcedureBuilder Add(string name, object? value, SqlDbType? dbType = null)
{
if (value is null) return this;
_sql.AppendLine($"{_separator}@{name} = @{name}");
_execSql.AppendLine($"{_separator}@{name} = @{name}");
_separator = ',';
if (dbType.HasValue)
@@ -32,7 +29,7 @@ internal sealed class StoredProcedureBuilder
public StoredProcedureBuilder AddOutput(string name, SqlDbType dbType)
{
_sql.AppendLine($"{_separator}@{name} = @{name} OUTPUT");
_execSql.AppendLine($"{_separator}@{name} = @{name} OUTPUT");
_separator = ',';
_parameters.Add(new SqlParameter
@@ -45,7 +42,17 @@ internal sealed class StoredProcedureBuilder
return this;
}
public string BuildSql() => _sql.ToString();
public string BuildSql()
{
if (returnVariable is null)
return _execSql.ToString();
return new StringBuilder()
.AppendLine($"DECLARE @{returnVariable} SMALLINT = 0;")
.Append(_execSql).AppendLine(";")
.AppendLine($"SELECT @{returnVariable};")
.ToString();
}
public SqlParameter[] BuildParameters() => [.. _parameters];

View File

@@ -7,7 +7,6 @@ using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Options;
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
using System.Data;
using System.Text;
namespace ReC.Application.Common.Procedures.UpdateProcedure;
@@ -38,7 +37,7 @@ public class UpdateObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
{
public async Task<int> Handle(UpdateObjectProcedure request, CancellationToken cancel)
{
var sp = new StoredProcedureBuilder("EXEC @RC = [dbo].[PRREC_UPDATE_OBJECT]")
var sp = new StoredProcedureBuilder("[dbo].[PRREC_UPDATE_OBJECT]", "RC")
.Add("pENTITY", request.Entity)
.Add("pGUID", request.Id)
.Add("pCHANGED_WHO", request.ChangedWho)
@@ -91,15 +90,9 @@ public class UpdateObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
.Add("pRESULT_HEADER", request.Result.Header)
.Add("pRESULT_BODY", request.Result.Body);
var sql = new StringBuilder()
.AppendLine("DECLARE @RC SMALLINT = 0;")
.Append(sp.BuildSql()).AppendLine(";")
.AppendLine("SELECT @RC;")
.ToString();
try
{
var result = await repo.ExecuteQueryRawAsync(sql, sp.BuildParameters(), cancel);
var result = await repo.ExecuteQueryRawAsync(sp.BuildSql(), sp.BuildParameters(), cancel);
if (result > 0)
{

View File

@@ -57,10 +57,6 @@ public class InsertObjectProcedureValidator : AbstractValidator<InsertObjectProc
RuleFor(x => x.Result!.ActionId)
.NotNull()
.WithMessage("RESULT requires ResultActionId (maps to @pRESULT_ACTION_ID).");
RuleFor(x => x.Result!)
.Must(r => r.StatusId != null || r.Info != null || r.Error != null)
.WithMessage("RESULT requires at least one of: StatusId, Info, or Error.");
});
// ENDPOINT_PARAMS validation

View File

@@ -146,7 +146,7 @@ public class InvokeRecActionViewCommandHandler(
await sender.Send(new InsertResultCommand()
{
StatusId = statusCode,
Status = response.StatusCode.ToRecStatus(),
ActionId = action.Id,
Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }),
Body = resBody,
@@ -157,6 +157,7 @@ public class InvokeRecActionViewCommandHandler(
{
await sender.Send(new InsertResultCommand()
{
Status = RecStatus.Failed,
ActionId = action.Id,
Error = ex.ToString(),
Type = ResultType.Main

View File

@@ -7,7 +7,7 @@ namespace ReC.Application.Results.Commands;
public record InsertResultCommand : IInsertProcedure
{
public long? ActionId { get; set; }
public short? StatusId { get; set; }
public required RecStatus Status { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
public string? Info { get; set; }

View File

@@ -0,0 +1,431 @@
using ReC.Domain.Views;
namespace ReC.Domain.Constants;
/// <summary>
/// Represents status codes used to indicate the outcome of an operation.
/// <para>
/// Includes all standard HTTP status codes as defined in <see cref="System.Net.HttpStatusCode"/>,
/// as well as custom non-HTTP status codes for internal operation results.
/// </para>
/// </summary>
/// <seealso cref="RecStatusExtensions"/>
public enum RecStatus : short
{
/// <summary>
/// Indicates that a SQL query executed successfully (value 0).
/// Used as the result status when <see cref="RecActionView.PreprocessingQuery"/>
/// or <see cref="RecActionView.PostprocessingQuery"/> completes without error.
/// </summary>
QuerySuccess = 0,
/// <summary>
/// Indicates that an operation failed at any stage (value 999).
/// This includes SQL query failures during preprocessing/postprocessing,
/// HTTP request errors, or any other unhandled exception within the action pipeline.
/// </summary>
Failed = 999,
//
// Summary:
// Equivalent to HTTP status 100. System.Net.HttpStatusCode.Continue indicates that
// the client can continue with its request.
Continue = 100,
//
// Summary:
// Equivalent to HTTP status 101. System.Net.HttpStatusCode.SwitchingProtocols indicates
// that the protocol version or protocol is being changed.
SwitchingProtocols = 101,
//
// Summary:
// Equivalent to HTTP status 102. System.Net.HttpStatusCode.Processing indicates
// that the server has accepted the complete request but hasn't completed it yet.
Processing = 102,
//
// Summary:
// Equivalent to HTTP status 103. System.Net.HttpStatusCode.EarlyHints indicates
// to the client that the server is likely to send a final response with the header
// fields included in the informational response.
EarlyHints = 103,
//
// Summary:
// Equivalent to HTTP status 200. System.Net.HttpStatusCode.OK indicates that the
// request succeeded and that the requested information is in the response. This
// is the most common status code to receive.
OK = 200,
//
// Summary:
// Equivalent to HTTP status 201. System.Net.HttpStatusCode.Created indicates that
// the request resulted in a new resource created before the response was sent.
Created = 201,
//
// Summary:
// Equivalent to HTTP status 202. System.Net.HttpStatusCode.Accepted indicates that
// the request has been accepted for further processing.
Accepted = 202,
//
// Summary:
// Equivalent to HTTP status 203. System.Net.HttpStatusCode.NonAuthoritativeInformation
// indicates that the returned meta information is from a cached copy instead of
// the origin server and therefore may be incorrect.
NonAuthoritativeInformation = 203,
//
// Summary:
// Equivalent to HTTP status 204. System.Net.HttpStatusCode.NoContent indicates
// that the request has been successfully processed and that the response is intentionally
// blank.
NoContent = 204,
//
// Summary:
// Equivalent to HTTP status 205. System.Net.HttpStatusCode.ResetContent indicates
// that the client should reset (not reload) the current resource.
ResetContent = 205,
//
// Summary:
// Equivalent to HTTP status 206. System.Net.HttpStatusCode.PartialContent indicates
// that the response is a partial response as requested by a GET request that includes
// a byte range.
PartialContent = 206,
//
// Summary:
// Equivalent to HTTP status 207. System.Net.HttpStatusCode.MultiStatus indicates
// multiple status codes for a single response during a Web Distributed Authoring
// and Versioning (WebDAV) operation. The response body contains XML that describes
// the status codes.
MultiStatus = 207,
//
// Summary:
// Equivalent to HTTP status 208. System.Net.HttpStatusCode.AlreadyReported indicates
// that the members of a WebDAV binding have already been enumerated in a preceding
// part of the multistatus response, and are not being included again.
AlreadyReported = 208,
//
// Summary:
// Equivalent to HTTP status 226. System.Net.HttpStatusCode.IMUsed indicates that
// the server has fulfilled a request for the resource, and the response is a representation
// of the result of one or more instance-manipulations applied to the current instance.
IMUsed = 226,
//
// Summary:
// Equivalent to HTTP status 300. System.Net.HttpStatusCode.Ambiguous indicates
// that the requested information has multiple representations. The default action
// is to treat this status as a redirect and follow the contents of the Location
// header associated with this response. Ambiguous is a synonym for MultipleChoices.
Ambiguous = 300,
//
// Summary:
// Equivalent to HTTP status 300. System.Net.HttpStatusCode.MultipleChoices indicates
// that the requested information has multiple representations. The default action
// is to treat this status as a redirect and follow the contents of the Location
// header associated with this response. MultipleChoices is a synonym for Ambiguous.
MultipleChoices = 300,
//
// Summary:
// Equivalent to HTTP status 301. System.Net.HttpStatusCode.Moved indicates that
// the requested information has been moved to the URI specified in the Location
// header. The default action when this status is received is to follow the Location
// header associated with the response. When the original request method was POST,
// the redirected request will use the GET method. Moved is a synonym for MovedPermanently.
Moved = 301,
//
// Summary:
// Equivalent to HTTP status 301. System.Net.HttpStatusCode.MovedPermanently indicates
// that the requested information has been moved to the URI specified in the Location
// header. The default action when this status is received is to follow the Location
// header associated with the response. MovedPermanently is a synonym for Moved.
MovedPermanently = 301,
//
// Summary:
// Equivalent to HTTP status 302. System.Net.HttpStatusCode.Found indicates that
// the requested information is located at the URI specified in the Location header.
// The default action when this status is received is to follow the Location header
// associated with the response. When the original request method was POST, the
// redirected request will use the GET method. Found is a synonym for Redirect.
Found = 302,
//
// Summary:
// Equivalent to HTTP status 302. System.Net.HttpStatusCode.Redirect indicates that
// the requested information is located at the URI specified in the Location header.
// The default action when this status is received is to follow the Location header
// associated with the response. When the original request method was POST, the
// redirected request will use the GET method. Redirect is a synonym for Found.
Redirect = 302,
//
// Summary:
// Equivalent to HTTP status 303. System.Net.HttpStatusCode.RedirectMethod automatically
// redirects the client to the URI specified in the Location header as the result
// of a POST. The request to the resource specified by the Location header will
// be made with a GET. RedirectMethod is a synonym for SeeOther.
RedirectMethod = 303,
//
// Summary:
// Equivalent to HTTP status 303. System.Net.HttpStatusCode.SeeOther automatically
// redirects the client to the URI specified in the Location header as the result
// of a POST. The request to the resource specified by the Location header will
// be made with a GET. SeeOther is a synonym for RedirectMethod.
SeeOther = 303,
//
// Summary:
// Equivalent to HTTP status 304. System.Net.HttpStatusCode.NotModified indicates
// that the client's cached copy is up to date. The contents of the resource are
// not transferred.
NotModified = 304,
//
// Summary:
// Equivalent to HTTP status 305. System.Net.HttpStatusCode.UseProxy indicates that
// the request should use the proxy server at the URI specified in the Location
// header.
UseProxy = 305,
//
// Summary:
// Equivalent to HTTP status 306. System.Net.HttpStatusCode.Unused is a proposed
// extension to the HTTP/1.1 specification that is not fully specified.
Unused = 306,
//
// Summary:
// Equivalent to HTTP status 307. System.Net.HttpStatusCode.RedirectKeepVerb indicates
// that the request information is located at the URI specified in the Location
// header. The default action when this status is received is to follow the Location
// header associated with the response. When the original request method was POST,
// the redirected request will also use the POST method. RedirectKeepVerb is a synonym
// for TemporaryRedirect.
RedirectKeepVerb = 307,
//
// Summary:
// Equivalent to HTTP status 307. System.Net.HttpStatusCode.TemporaryRedirect indicates
// that the request information is located at the URI specified in the Location
// header. The default action when this status is received is to follow the Location
// header associated with the response. When the original request method was POST,
// the redirected request will also use the POST method. TemporaryRedirect is a
// synonym for RedirectKeepVerb.
TemporaryRedirect = 307,
//
// Summary:
// Equivalent to HTTP status 308. System.Net.HttpStatusCode.PermanentRedirect indicates
// that the request information is located at the URI specified in the Location
// header. The default action when this status is received is to follow the Location
// header associated with the response. When the original request method was POST,
// the redirected request will also use the POST method.
PermanentRedirect = 308,
//
// Summary:
// Equivalent to HTTP status 400. System.Net.HttpStatusCode.BadRequest indicates
// that the request could not be understood by the server. System.Net.HttpStatusCode.BadRequest
// is sent when no other error is applicable, or if the exact error is unknown or
// does not have its own error code.
BadRequest = 400,
//
// Summary:
// Equivalent to HTTP status 401. System.Net.HttpStatusCode.Unauthorized indicates
// that the requested resource requires authentication. The WWW-Authenticate header
// contains the details of how to perform the authentication.
Unauthorized = 401,
//
// Summary:
// Equivalent to HTTP status 402. System.Net.HttpStatusCode.PaymentRequired is reserved
// for future use.
PaymentRequired = 402,
//
// Summary:
// Equivalent to HTTP status 403. System.Net.HttpStatusCode.Forbidden indicates
// that the server refuses to fulfill the request.
Forbidden = 403,
//
// Summary:
// Equivalent to HTTP status 404. System.Net.HttpStatusCode.NotFound indicates that
// the requested resource does not exist on the server.
NotFound = 404,
//
// Summary:
// Equivalent to HTTP status 405. System.Net.HttpStatusCode.MethodNotAllowed indicates
// that the request method (POST or GET) is not allowed on the requested resource.
MethodNotAllowed = 405,
//
// Summary:
// Equivalent to HTTP status 406. System.Net.HttpStatusCode.NotAcceptable indicates
// that the client has indicated with Accept headers that it will not accept any
// of the available representations of the resource.
NotAcceptable = 406,
//
// Summary:
// Equivalent to HTTP status 407. System.Net.HttpStatusCode.ProxyAuthenticationRequired
// indicates that the requested proxy requires authentication. The Proxy-authenticate
// header contains the details of how to perform the authentication.
ProxyAuthenticationRequired = 407,
//
// Summary:
// Equivalent to HTTP status 408. System.Net.HttpStatusCode.RequestTimeout indicates
// that the client did not send a request within the time the server was expecting
// the request.
RequestTimeout = 408,
//
// Summary:
// Equivalent to HTTP status 409. System.Net.HttpStatusCode.Conflict indicates that
// the request could not be carried out because of a conflict on the server.
Conflict = 409,
//
// Summary:
// Equivalent to HTTP status 410. System.Net.HttpStatusCode.Gone indicates that
// the requested resource is no longer available.
Gone = 410,
//
// Summary:
// Equivalent to HTTP status 411. System.Net.HttpStatusCode.LengthRequired indicates
// that the required Content-length header is missing.
LengthRequired = 411,
//
// Summary:
// Equivalent to HTTP status 412. System.Net.HttpStatusCode.PreconditionFailed indicates
// that a condition set for this request failed, and the request cannot be carried
// out. Conditions are set with conditional request headers like If-Match, If-None-Match,
// or If-Unmodified-Since.
PreconditionFailed = 412,
//
// Summary:
// Equivalent to HTTP status 413. System.Net.HttpStatusCode.RequestEntityTooLarge
// indicates that the request is too large for the server to process.
RequestEntityTooLarge = 413,
//
// Summary:
// Equivalent to HTTP status 414. System.Net.HttpStatusCode.RequestUriTooLong indicates
// that the URI is too long.
RequestUriTooLong = 414,
//
// Summary:
// Equivalent to HTTP status 415. System.Net.HttpStatusCode.UnsupportedMediaType
// indicates that the request is an unsupported type.
UnsupportedMediaType = 415,
//
// Summary:
// Equivalent to HTTP status 416. System.Net.HttpStatusCode.RequestedRangeNotSatisfiable
// indicates that the range of data requested from the resource cannot be returned,
// either because the beginning of the range is before the beginning of the resource,
// or the end of the range is after the end of the resource.
RequestedRangeNotSatisfiable = 416,
//
// Summary:
// Equivalent to HTTP status 417. System.Net.HttpStatusCode.ExpectationFailed indicates
// that an expectation given in an Expect header could not be met by the server.
ExpectationFailed = 417,
//
// Summary:
// Equivalent to HTTP status 421. System.Net.HttpStatusCode.MisdirectedRequest indicates
// that the request was directed at a server that is not able to produce a response.
MisdirectedRequest = 421,
//
// Summary:
// Equivalent to HTTP status 422. System.Net.HttpStatusCode.UnprocessableEntity
// indicates that the request was well-formed but was unable to be followed due
// to semantic errors. UnprocessableEntity is a synonym for UnprocessableContent.
UnprocessableEntity = 422,
//
// Summary:
// Equivalent to HTTP status 422. System.Net.HttpStatusCode.UnprocessableContent
// indicates that the request was well-formed but was unable to be followed due
// to semantic errors. UnprocessableContent is a synonym for UnprocessableEntity.
UnprocessableContent = 422,
//
// Summary:
// Equivalent to HTTP status 423. System.Net.HttpStatusCode.Locked indicates that
// the source or destination resource is locked.
Locked = 423,
//
// Summary:
// Equivalent to HTTP status 424. System.Net.HttpStatusCode.FailedDependency indicates
// that the method couldn't be performed on the resource because the requested action
// depended on another action and that action failed.
FailedDependency = 424,
//
// Summary:
// Equivalent to HTTP status 426. System.Net.HttpStatusCode.UpgradeRequired indicates
// that the client should switch to a different protocol such as TLS/1.0.
UpgradeRequired = 426,
//
// Summary:
// Equivalent to HTTP status 428. System.Net.HttpStatusCode.PreconditionRequired
// indicates that the server requires the request to be conditional.
PreconditionRequired = 428,
//
// Summary:
// Equivalent to HTTP status 429. System.Net.HttpStatusCode.TooManyRequests indicates
// that the user has sent too many requests in a given amount of time.
TooManyRequests = 429,
//
// Summary:
// Equivalent to HTTP status 431. System.Net.HttpStatusCode.RequestHeaderFieldsTooLarge
// indicates that the server is unwilling to process the request because its header
// fields (either an individual header field or all the header fields collectively)
// are too large.
RequestHeaderFieldsTooLarge = 431,
//
// Summary:
// Equivalent to HTTP status 451. System.Net.HttpStatusCode.UnavailableForLegalReasons
// indicates that the server is denying access to the resource as a consequence
// of a legal demand.
UnavailableForLegalReasons = 451,
//
// Summary:
// Equivalent to HTTP status 500. System.Net.HttpStatusCode.InternalServerError
// indicates that a generic error has occurred on the server.
InternalServerError = 500,
//
// Summary:
// Equivalent to HTTP status 501. System.Net.HttpStatusCode.NotImplemented indicates
// that the server does not support the requested function.
NotImplemented = 501,
//
// Summary:
// Equivalent to HTTP status 502. System.Net.HttpStatusCode.BadGateway indicates
// that an intermediate proxy server received a bad response from another proxy
// or the origin server.
BadGateway = 502,
//
// Summary:
// Equivalent to HTTP status 503. System.Net.HttpStatusCode.ServiceUnavailable indicates
// that the server is temporarily unavailable, usually due to high load or maintenance.
ServiceUnavailable = 503,
//
// Summary:
// Equivalent to HTTP status 504. System.Net.HttpStatusCode.GatewayTimeout indicates
// that an intermediate proxy server timed out while waiting for a response from
// another proxy or the origin server.
GatewayTimeout = 504,
//
// Summary:
// Equivalent to HTTP status 505. System.Net.HttpStatusCode.HttpVersionNotSupported
// indicates that the requested HTTP version is not supported by the server.
HttpVersionNotSupported = 505,
//
// Summary:
// Equivalent to HTTP status 506. System.Net.HttpStatusCode.VariantAlsoNegotiates
// indicates that the chosen variant resource is configured to engage in transparent
// content negotiation itself and, therefore, isn't a proper endpoint in the negotiation
// process.
VariantAlsoNegotiates = 506,
//
// Summary:
// Equivalent to HTTP status 507. System.Net.HttpStatusCode.InsufficientStorage
// indicates that the server is unable to store the representation needed to complete
// the request.
InsufficientStorage = 507,
//
// Summary:
// Equivalent to HTTP status 508. System.Net.HttpStatusCode.LoopDetected indicates
// that the server terminated an operation because it encountered an infinite loop
// while processing a WebDAV request with "Depth: infinity". This status code is
// meant for backward compatibility with clients not aware of the 208 status code
// System.Net.HttpStatusCode.AlreadyReported appearing in multistatus response bodies.
LoopDetected = 508,
//
// Summary:
// Equivalent to HTTP status 510. System.Net.HttpStatusCode.NotExtended indicates
// that further extensions to the request are required for the server to fulfill
// it.
NotExtended = 510,
//
// Summary:
// Equivalent to HTTP status 511. System.Net.HttpStatusCode.NetworkAuthenticationRequired
// indicates that the client needs to authenticate to gain network access; it's
// intended for use by intercepting proxies used to control access to the network.
NetworkAuthenticationRequired = 511
}

View File

@@ -0,0 +1,34 @@
using System.Net;
namespace ReC.Domain.Constants;
public static class RecStatusExtensions
{
public static HttpStatusCode? ToHttpStatusCode(this RecStatus status)
{
int code = (int)status;
if (Enum.IsDefined(typeof(HttpStatusCode), code))
{
return (HttpStatusCode)code;
}
return null;
}
public static bool IsSuccess(this HttpStatusCode code)
{
int value = (int)code;
return value >= 200 && value <= 299;
}
public static bool IsSuccess(this RecStatus status)
=> status switch
{
RecStatus.QuerySuccess => true,
RecStatus.Failed => false,
_ => status.ToHttpStatusCode() is HttpStatusCode httpStatus && httpStatus.IsSuccess()
};
public static RecStatus ToRecStatus(this HttpStatusCode code) => (RecStatus)(short)code;
}

View File

@@ -1,6 +1,6 @@
namespace ReC.Domain.Constants;
public enum ResultType
public enum ResultType : byte
{
Pre = 1,
Main,

View File

@@ -25,7 +25,7 @@ public class ResultView
public string? ProfileName { get; set; }
[Column("STATUS_ID")]
public short? StatusCode { get; set; }
public RecStatus Status { get; set; }
[Column("STATUS")]
public string? StatusName { get; set; }

View File

@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
namespace ReC.Tests.Application.Behaviors;
@@ -211,46 +210,40 @@ public class InvokeActionTests
#region ReplacePlaceholders - Exception Tests
[Test]
public void ReplacePlaceholders_UnresolvableColumn_ThrowsPlaceholderResolutionException()
public void ReplacePlaceholders_UnresolvableColumn_ReturnsNull()
{
var input = "WHERE X = {#INT#NON_EXISTING}";
var ex = Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders(_foo, _bar, _fuz));
Assert.That(ex!.ColumnName, Is.EqualTo("NON_EXISTING"));
Assert.That(ex.Placeholder, Is.EqualTo("{#INT#NON_EXISTING}"));
Assert.That(ex.Input, Is.EqualTo(input));
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo("WHERE X = NULL"));
}
[Test]
public void ReplacePlaceholders_NoObjectsProvided_ThrowsPlaceholderResolutionException()
public void ReplacePlaceholders_NoObjectsProvided_ReturnsNull()
{
var input = "WHERE X = {#INT#BAZ}";
Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders());
var result = input.ReplacePlaceholders();
Assert.That(result, Is.EqualTo("WHERE X = NULL"));
}
[Test]
public void ReplacePlaceholders_ObjectWithoutColumnAttributes_ThrowsPlaceholderResolutionException()
public void ReplacePlaceholders_ObjectWithoutColumnAttributes_ReturnsNull()
{
var model = new NoColumnModel { Id = 1, Name = "Test" };
var input = "WHERE X = {#INT#Id}";
Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders(model));
var result = input.ReplacePlaceholders(model);
Assert.That(result, Is.EqualTo("WHERE X = NULL"));
}
[Test]
public void ReplacePlaceholders_MixedResolvableAndUnresolvable_ThrowsOnUnresolvable()
public void ReplacePlaceholders_MixedResolvableAndUnresolvable_ReturnsNullForUnresolvable()
{
var input = "WHERE BAZ = {#INT#BAZ} AND X = {#INT#UNKNOWN}";
var ex = Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders(_foo, _bar, _fuz));
Assert.That(ex!.ColumnName, Is.EqualTo("UNKNOWN"));
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo("WHERE BAZ = 2 AND X = NULL"));
}
#endregion

View File

@@ -1,3 +1,4 @@
using System.Net;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
@@ -6,6 +7,7 @@ using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Results.Commands;
using ReC.Domain.Constants;
using ReC.Tests.Application;
namespace ReC.Tests.Application.Results;
@@ -23,7 +25,7 @@ public class ResultProcedureTests : RecApplicationTestBase
[Test]
public async Task InsertResultProcedure_runs_via_mediator()
{
var procedure = new InsertResultCommand { ActionId = 1, StatusId = 200, Header = "h", Body = "b", Type = Domain.Constants.ResultType.Main };
var procedure = new InsertResultCommand { ActionId = 1, Status = HttpStatusCode.OK.ToRecStatus(), Header = "h", Body = "b", Type = Domain.Constants.ResultType.Main };
var (sender, scope) = CreateScopedSender();
using var _ = scope;