{"id":21623036,"url":"https://github.com/pandatecham/be-lib-response-crafter","last_synced_at":"2025-08-27T11:15:49.482Z","repository":{"id":209605737,"uuid":"724035027","full_name":"PandaTechAM/be-lib-response-crafter","owner":"PandaTechAM","description":"Standard response DTOs and ExceptionHandler with custom exceptions","archived":false,"fork":false,"pushed_at":"2025-08-15T16:08:02.000Z","size":296,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"development","last_synced_at":"2025-08-15T22:52:48.294Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PandaTechAM.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-11-27T09:08:19.000Z","updated_at":"2025-08-15T16:07:44.000Z","dependencies_parsed_at":"2023-11-28T09:26:01.766Z","dependency_job_id":"e10db649-5aa3-4ad6-9055-bc86eb248397","html_url":"https://github.com/PandaTechAM/be-lib-response-crafter","commit_stats":null,"previous_names":["pandatecham/be-lib-response-crafter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/PandaTechAM/be-lib-response-crafter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-response-crafter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-response-crafter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-response-crafter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-response-crafter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PandaTechAM","download_url":"https://codeload.github.com/PandaTechAM/be-lib-response-crafter/tar.gz/refs/heads/development","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PandaTechAM%2Fbe-lib-response-crafter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272325392,"owners_count":24914642,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-27T02:00:09.397Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-25T00:11:21.162Z","updated_at":"2025-08-27T11:15:49.473Z","avatar_url":"https://github.com/PandaTechAM.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pandatech.ResponseCrafter\n\nA lightweight exception handling and logging package for ASP.NET Core (Minimal APIs with first-class **SignalR**\nsupport.  \nIt standardizes server-side error handling and produces consistent, frontend-friendly error payloads with messages\nsuitable for localization.\n\n---\n\n## Features\n\n- **Unified exception handling** for REST \u0026 SignalR (built on ASP.NET Core’s `IExceptionHandler` and SignalR\n  `IHubFilter`).\n- **Visibility modes** (`Public` / `Private`) to control how much detail is exposed in responses (e.g., hide 5xx details\n  in Public).\n- **Frontend-friendly messages** via configurable naming conventions (e.g., snake_case).\n- **Predefined HTTP exceptions** covering common 4xx/5xx cases + helpers (`ThrowIf...`) to reduce boilerplate.\n- **Consistent logging** with correlation IDs (`RequestId`, `TraceId`) and level split (4xx → warning, 5xx → error).\n- **SignalR** error envelope with `invocation_id` echo and `ReceiveError` channel.\n\n---\n\n## Installation\n\nUse either NuGet Package Manager or the CLI:\n\n```bash\ndotnet add package Pandatech.ResponseCrafter\n# or\nInstall-Package Pandatech.ResponseCrafter\n```\n\n---\n\n## Quick Start (Minimal API)\n\n**program.cs**\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\n// 1) Register ResponseCrafter (optional naming convention)\nbuilder.AddResponseCrafter(NamingConvention.ToSnakeCase);\n\n// 2) Configure SignalR (optional). The filter applies package behavior to hubs.\nbuilder.Services.AddSignalR(options =\u003e options.AddFilter\u003cSignalRExceptionFilter\u003e());\n\nvar app = builder.Build();\n\n// 3) Use ResponseCrafter middleware/exception handler\napp.UseResponseCrafter();\n\napp.MapGet(\"/ping\", () =\u003e \"pong\");\n\napp.MapHub\u003cChatHub\u003e(\"/hubs/chat\");\n\napp.Run();\n```\n\n**appsettings.json**\n\n```json\n{\n    \"ResponseCrafterVisibility\": \"Public\"\n}\n```\n\n- **Public:** 4xx → detailed as defined; 5xx → generic message (no sensitive details).\n- **Private:** 4xx/5xx → expands with verbose diagnostics (where available).\n\n\u003e **Note:** By default, `ResponseCrafter` suppresses the duplicate framework error log from\n`Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware`.\n\u003e If you prefer to keep that log, opt out:\n\u003e ```csharp\n\u003e builder.AddResponseCrafter(\u003e NamingConvention.ToSnakeCase,\u003e suppressExceptionHandlerMiddlewareLog: false);\n\u003e ```\n\n```\n---\n\n## Supported HTTP Status Codes\n\n| Code | Description                                       |\n|:-----|:--------------------------------------------------|\n| 200  | Request succeeded.                                |\n| 202  | Request accepted (e.g. order enqueued).           |\n| 400  | Invalid request parameters or duplicate requests. |\n| 401  | Authentication failed.                            |\n| 403  | Insufficient permissions.                         |\n| 404  | Resource not found.                               |\n| 409  | Conflict (e.g. concurrency violation).            |\n| 429  | Too many requests.                                |\n| 500  | Server encountered an unexpected error.           |\n| 503  | Service unavailable.                              |\n\n---\n\n### REST (HTTP/JSON)\n\n**Content type:** `application/json`\n\n```json\n{\n    \"RequestId\": \"0HMVFE0A284AM:00000001\",\n    \"TraceId\": \"a55582ab204162e66e124b0378776ab7\",\n    \"Instance\": \"POST - api.example.com:443/users/register\",\n    \"StatusCode\": 400,\n    \"Type\": \"BadRequestException\",\n    \"Errors\": {\n        \"email\": \"email_address_is_not_in_a_valid_format\",\n        \"password\": \"password_must_be_at_least_8_characters_long\"\n    },\n    \"Message\": \"the_request_was_invalid_or_cannot_be_otherwise_served.\"\n}\n```\n\n**Fields**\n\n- **RequestId** – ASP.NET Core `HttpContext.TraceIdentifier`.\n- **TraceId** – distributed trace id (W3C, e.g., `Activity.Current?.TraceId`).\n- **Instance** – contextual info (e.g., `METHOD - host:port/path`).\n- **StatusCode** – HTTP status code associated with the error.\n- **Type** – short descriptor (CLR exception name for API exceptions; `\"InternalServerError\"` for 5xx in Public).\n- **Errors** – `Dictionary\u003cstring, string\u003e` of field-level messages.\n- **Message** – human- or key-like description.\n\n### SignalR (ReceiveError)\n\nErrors are sent to the calling client on **`ReceiveError`**.\n\n```json\n{\n    \"TraceId\": \"a55582ab204162e66e124b0378776ab7\",\n    \"InvocationId\": \"0HMVFE0A0HMVFE0A284AMHMV00HMVFE0A284AM0A284AM\",\n    \"Instance\": \"SendMessage\",\n    \"StatusCode\": 400,\n    \"Errors\": {\n        \"email\": \"email_address_is_not_in_a_valid_format\",\n        \"password\": \"password_must_be_at_least_8_characters_long\"\n    },\n    \"Message\": \"the_request_was_invalid_or_cannot_be_otherwise_served.\"\n}\n```\n\n**Caller contract**\n\n- Include a non-empty **`InvocationId`** in hub calls (see `HubArgument\u003cT\u003e`).\n- The same `InvocationId` is echoed in errors to correlate requests/responses.\n\n---\n\n## Naming Conventions\n\nUse `NamingConvention` to transform messages (`Message` and `Errors` values).  \n**Recommendation:** `ToSnakeCase` or `ToUpperSnakeCase`.\n\n```csharp\npublic enum NamingConvention\n{\nDefault = 0,\nToSnakeCase = 1,\nToPascalCase = 2,\nToCamelCase = 3,\nToKebabCase = 4,\nToTitleCase = 5,\nToHumanCase = 6,\nToUpperSnakeCase = 7\n}\n```\n\n---\n\n## Custom Exceptions\n\nExtend the base `ApiException` (package type) to create business-specific errors that serialize consistently.\n\n**Predefined exceptions:**\n\n- `BadRequestException`\n- `UnauthorizedException`\n- `PaymentRequiredException`\n- `ForbiddenException`\n- `NotFoundException`\n- `ConflictException`\n- `TooManyRequestsException`\n- `InternalServerErrorException`\n- `ServiceUnavailableException`\n\n**Example: create a custom domain exception**\n\n```csharp\nusing ResponseCrafter.HttpExceptions;\n\npublic sealed class OrderLimitExceededException : ApiException\n{\npublic OrderLimitExceededException(string? message = null)\n: base(statusCode: 400, message ?? \"order_limit_exceeded\")\n{\nErrors = new() { [\"limit\"] = \"maximum_daily_order_limit_reached\" };\n}\n}\n```\n\n**Usage in an endpoint**\n\n```csharp\napp.MapPost(\"/orders\", (CreateOrderRequest req) =\u003e\n{\nif (req.Quantity \u003e 100)\nthrow new OrderLimitExceededException();\n\n// normal work...\nreturn Results.Accepted();\n});\n```\n\n---\n\n## Helper Methods (ThrowIf…)\n\nUse the built-in helper methods to reduce guard boilerplate.\n\n```csharp\ndecimal? price = -10.5m;\n// 400 Bad Request\nBadRequestException.ThrowIfNullOrNegative(price, \"price_is_negative\");\n// 500 Internal Server Error\nInternalServerErrorException.ThrowIfNullOrNegative(price, \"price_is_negative\");\n\nstring? username = \"   \";\n// 400 Bad Request\nBadRequestException.ThrowIfNullOrWhiteSpace(username, \"please_provide_username\");\n// 404 Not Found\nNotFoundException.ThrowIfNullOrWhiteSpace(username);\n// 500 Internal Server Error\nInternalServerErrorException.ThrowIfNullOrWhiteSpace(username, \"username_required\");\n\nList\u003cint\u003e tags = [];\n// 400 Bad Request\nBadRequestException.ThrowIfNullOrEmpty(tags, \"please_provide_tags\");\n// 404 Not Found\nNotFoundException.ThrowIfNullOrEmpty(tags);\n// 500 Internal Server Error\nInternalServerErrorException.ThrowIfNullOrEmpty(tags, \"tags_required\");\n\nobject? user = null;\n// 400 Bad Request\nBadRequestException.ThrowIfNull(user, \"please_provide_user\");\n// 404 Not Found\nNotFoundException.ThrowIfNull(user, \"user_not_found\");\n// 500 Internal Server Error\nInternalServerErrorException.ThrowIfNull(user, \"user_required\");\n\nbool userUnauthorized = false;\n// 401 Unauthorized\nUnauthorizedException.ThrowIf(userUnauthorized, \"user_is_unauthorized\");\n// 500 Internal Server Error\nInternalServerErrorException.ThrowIf(userUnauthorized, \"authorization_check_failed\");\n```\n\n---\n\n## SignalR Integration\n\n**Register the filter**\n\n```csharp\nbuilder.Services.AddSignalR(options =\u003e options.AddFilter\u003cSignalRExceptionFilter\u003e());\n```\n\n**Define the hub argument contract**\n\n```csharp\npublic interface IHubArgument\n{\n    string InvocationId { get; set; }\n}\n\npublic class HubArgument\u003cT\u003e : IHubArgument\n{\n    public required string InvocationId { get; set; }\n    public required T Argument { get; set; }\n}\n```\n\n**Hub method example**\n\n```csharp\npublic class ChatHub : Hub\n{\npublic async Task SendMessage(HubArgument\u003cMessage\u003e hubArgument)\n{\n// Example: intentionally throwing to demonstrate an error path\nthrow new BadRequestException(\"invalid_message_format\");\n\n    // Normally:\n    // await Clients.All.SendAsync(\"ReceiveMessage\", hubArgument.Argument);\n\n}\n}\n\npublic class Message : IHubArgument\n{\n    public required string Message {get; set;}\n    public required string InvocationId { get; set; } \n}\n```\n\n**Client expectation**\n\n- On error, server sends **`ReceiveError`** to the **caller** with the structure shown above.\n- `InvocationId` in the request is echoed back in the error.\n- Multi-argument hub methods are supported as long as at least one argument implements `IHubArgument` (\n  backward-compatible).\n\n---\n\n## Logging \u0026 Telemetry\n\n- **4xx** → logged as **Warning**\n- **5xx** → logged as **Error**\n- Correlate using **RequestId** (REST) / **InvocationId** (SignalR) and **TraceId** across services.\n- The package emits enough context for centralized log aggregation systems.\n\n\u003e **Tip:** You can create logging scopes around `TraceId`/`RequestId` in your app if desired.\n\n---\n\n## Built-in Mappings \u0026 Behavior\n\n- `DbUpdateConcurrencyException` → **409 Conflict**\n- `BadHttpRequestException` → **400 Bad Request** with a stable “invalid payload” style message\n- `GridifyException` / `GridifyMapperException` → **400 Bad Request** with normalized messages\n\n**Unhandled exceptions**\n\n- **Public** → 5xx concealed behind a generic message + `Type = \"InternalServerError\"`.\n- **Private** → verbose error details are returned to aid debugging.\n\n---\n\n## Configuration Summary\n\n**Visibility**\n\n```json\n{\n    \"ResponseCrafterVisibility\": \"Public\"\n}\n```\n\n- Switch to `Private` for internal environments to expose verbose details (do not enable in production).\n\n**Naming Convention (optional)**\n\n- In `program.cs`, pass your preferred convention:\n\n```csharp\nbuilder.AddResponseCrafter(NamingConvention.ToSnakeCase);\n```\n\n### Exception handler log suppression (optional)\n\nEnabled by default to avoid duplicate request-error logs from ASP.NET Core’s\n`ExceptionHandlerMiddleware`. This does not affect hosted service logs or other `Microsoft.*` categories.\n\n```csharp\n// Keep the framework error log (opt out of suppression)\nbuilder.AddResponseCrafter(\n    namingConvention: NamingConvention.ToSnakeCase,\n    suppressExceptionHandlerMiddlewareLog: false);\n\n```\n\n---\n\n## Frontend Integration Guidance\n\n- Treat `Message` and `Errors` values as **localization keys**.\n- Use `StatusCode` for UX-level branching; avoid coupling to `Type` names.\n- Show `RequestId` / `TraceId` in error overlays to speed up support.\n- For SignalR, correlate using `InvocationId`.\n\n---\n\n## Versioning \u0026 Compatibility\n\n- **No breaking changes** to existing fields without a major version bump.\n- We may add **optional** fields in minor versions—clients should ignore unknown fields.\n- The SignalR error event name remains **`ReceiveError`**.\n\n---\n\n## Limitations\n\n- Designed for **.NET 8+**.\n\n---\n\n## License\n\nMIT\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpandatecham%2Fbe-lib-response-crafter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpandatecham%2Fbe-lib-response-crafter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpandatecham%2Fbe-lib-response-crafter/lists"}