diff --git a/.github/instructions/project.instructions.md b/.github/instructions/project.instructions.md new file mode 100644 index 0000000..675fcbc --- /dev/null +++ b/.github/instructions/project.instructions.md @@ -0,0 +1,77 @@ +You are assisting on an open-source .NET API endpoint development project. +Default to changes that strengthen Onion Architecture, Clean Code, SOLID, security, and testability. + +## Project Overview (Current Scope) + +* **DataEngine**: A .NET API service aligned with **IDTA (Industrial Digital Twin Association) specifications** that dynamically generates Asset Administration Shell (AAS) structures (shell descriptors, submodels, and submodel elements). + - On each request, DataEngine loads a template from external registries/repositories. At this time, templates contain only structure and semantic IDs; they do not contain values. + - After DataEngine retrieves the template, it requests a plugin to provide the values needed to populate the template. + - Once DataEngine receives the values from the plugin, it fills the template and returns a complete AAS model to the client. + +### Plugin (General Concept) + +A **Plugin** is a separate .NET API service that acts as the **data provider** for DataEngine. + +Plugins are responsible for: + +* Accessing/storing business data (database, files, or external systems). +* Resolving semantic IDs requested by DataEngine. +* Returning metadata and submodel data via JSON schema-based contracts or IDs. +* Exposing a Plugin Manifest that describes supported semantic IDs and capabilities. + +### Registry & Repository (General Concept) + +The AAS **registry** and **repository** services expose template and descriptor endpoints that DataEngine consumes to retrieve templates. + +* These services are external platform dependencies. +* Registry/repository components are **not developed or maintained by this project**. + +## High-Level Architecture + +```mermaid +flowchart LR + + %% Users + UIUser[UI User] + APIUser[API User] + + %% External Access + AASViewer["AAS Viewer (UI)"] + APIGateway["API Gateway"] + + %% TwinEngine Components + DataEngine["DataEngine API"] + Plugin["Plugin API"] + + %% Plugin Storage + PluginDB[(Plugin Database)] + + %% External AAS Infrastructure + TemplateRegistry["AAS Template Registry (External)"] + SubmodelRepo["AAS Submodel Template Repository (External)"] + AASRegistry["AAS Registry (External)"] + + %% User Flows + UIUser --> AASViewer + APIUser --> APIGateway + AASViewer --> APIGateway + + %% Core Communication + APIGateway --> DataEngine + DataEngine --> Plugin + Plugin --> PluginDB + + %% External Registry/Repository Integration + DataEngine --> TemplateRegistry + DataEngine --> SubmodelRepo + DataEngine --> AASRegistry +``` + +### Flow Summary + +1. Clients (UI or API) send requests through the API Gateway to **DataEngine**. +2. DataEngine retrieves templates from external **AAS repositories/registries**. +3. Templates contain structure and semantic IDs but no values. +4. DataEngine requests semantic ID values from a **Plugin API** using a JSON schema structure. +5. Plugin resolves values from its database and returns them. +6. DataEngine populates the template and returns a complete AAS model to the client. diff --git a/.github/skills/architecture/SKILL.md b/.github/skills/architecture/SKILL.md new file mode 100644 index 0000000..6e1beff --- /dev/null +++ b/.github/skills/architecture/SKILL.md @@ -0,0 +1,66 @@ +--- +name: architecture +description: 'Ensure .NET/C# code meets best practices for the solution/project.' +--- + +## Architecture (Onion / Clean Architecture) +- Maintain strict layer separation: + - **DomainModel**: pure domain types only (no ASP.NET Core, no database/ADO.NET, no config/options, no logging). + - **ApplicationLogic**: use cases, domain/application services, interfaces/ports; depends only on DomainModel. + - **Infrastructure**: database, file system, external services; implements ApplicationLogic interfaces. + - **Api**: HTTP/controllers/serialization; delegates to ApplicationLogic. +- Dependency direction: **Api** → **ApplicationLogic** → **DomainModel**; **Infrastructure** → **ApplicationLogic**. +- Never leak infrastructure/ASP.NET types into DomainModel/ApplicationLogic (e.g., `HttpContext`, `ControllerBase`, `DbConnection`, provider-specific types). +- Prefer defining ports (interfaces) in **ApplicationLogic** and implementing them in **Infrastructure**. + +## API & DTOs +- Keep controllers thin: validation + request shaping + call handler/service + return mapped DTO. +- Do not return DomainModel types from controllers; always return DTOs under `Api/**/Responses`. +- Favor explicit request/response models over `JsonObject` when feasible; if dynamic JSON is required, isolate it to the API layer. +- Validation: + - Validate route/query/body inputs consistently. + - Keep validation rules close to request DTOs (or dedicated validators) and avoid duplication. + +## Error Handling +- Use centralized exception handling via `ErrorController` (avoid scattered controller-level try/catch). +- Return stable JSON error contracts with correct HTTP status codes. +- Map **base exception types** in `ErrorController` (for example `NotFoundException`, `BadRequestException`, `ServiceUnavailableException`) rather than listing every custom exception. +- Ensure custom exceptions inherit from the correct base type and define safe, reusable default messages (`public const string DefaultMessage`). +- Keep exception messages human-readable, consistent in tone, and safe for external consumers (no stack traces, internal endpoint details, or sensitive data). +- Keep exception organization consistent: + - `ApplicationLogic/Exceptions/Base` + - `ApplicationLogic/Exceptions/Application` + - `ApplicationLogic/Exceptions/Infrastructure` +- At boundaries, translate Infrastructure exceptions into Application exceptions before they reach the API. In some cases, the Infrastructure layer may directly throw application-level exceptions — especially if it has enough domain context to do so. +- Keep API mapping focused on base exception types; custom exceptions should inherit from the correct base. +- Define safe reusable messages in custom exceptions (prefer `public const string DefaultMessage`). +- Translate infrastructure exceptions at boundaries before returning to API consumers. +- Do not leak raw/internal exception details to clients; return stable error contracts and log safely. + +## Clean Code & SOLID +- Ensure SOLID principles compliance +- Avoid code duplication through base classes and utilities +- Prefer small, cohesive classes and methods. +- Watch for SRP violations (especially “handler/service” classes growing too large): extract focused collaborators. +- Prefer descriptive names; avoid abbreviations unless well-known. +- Avoid duplicate logic; introduce shared helpers only if reuse is clear. +- Follow existing patterns in this repo: + - `Api/**/Handler/*Handler.cs` orchestrates and delegates. + - `ApplicationLogic/Services/**` contains business/use-case logic. + - `Infrastructure/**` contains DB execution and provider implementations. + +## Configuration & Settings + +- Use strongly-typed configuration classes with data annotations +- Implement validation attributes (Required, NotEmptyOrWhitespace) +- Use IConfiguration binding for settings +- Support appsettings.json configuration files + +### Unit Test Method Naming +Use one consistent pattern (choose the closest match to surrounding tests): +- Preferred: `{MethodUnderTest}_When{Condition}_{ReturnsOrThrows}{Expected}` + - Example: `ExecuteQueryAsync_WhenNoRows_ThrowsResourceNotFoundException` +- Also acceptable (existing pattern): `{MethodUnderTest}_Should{Expectation}_When{Condition}` + - Example: `DecodeBase64_ShouldThrow_OnNullOrWhitespace` +- For async tests, keep the `Async` suffix in the method under test portion of the name. + diff --git a/.github/skills/asset-administration-shell-domain/SKILL.md b/.github/skills/asset-administration-shell-domain/SKILL.md new file mode 100644 index 0000000..4db06d9 --- /dev/null +++ b/.github/skills/asset-administration-shell-domain/SKILL.md @@ -0,0 +1,106 @@ +--- +name: asset-administration-shell-domain +description: Guidance for working with Asset Administration Shell (AAS) concepts, structures, and JSON representations when generating code or APIs +--- + +## 1. Core Concepts + +### Asset Administration Shell (AAS) +- Represents the **digital twin of an asset**. +- Key fields: + - `id` (globally unique identifier) + - `idShort` (human-readable identifier) + - `assetInformation` + - `submodels` + +Each asset can contain multiple **submodels** describing different aspects. + +--- + +## 2. Submodels +- Define specific aspects of an asset: + - Technical data + - Operational data + - Identification + - Documentation + - Condition monitoring + +**Structure:** +- `id` +- `idShort` +- `semanticId` +- `submodelElements` + +--- + +## 3. Submodel Elements +Submodel elements represent structured data inside a submodel. + +**Types include:** +- Property +- MultiLanguageProperty +- Range +- File +- Blob +- ReferenceElement +- RelationshipElement +- SubmodelElementCollection +- SubmodelElementList + +Elements can be **nested recursively**. + +--- + +## 4. Identifiers +- **id** → Globally unique identifier for AAS or Submodel. +- **idShort** → Human-readable identifier within the AAS structure. +- **semanticId** → Reference defining the semantic meaning of an element (often linked to IEC CDD or ECLASS dictionaries). + +--- + +## 5. References +Used to connect elements: +- **ModelReference** → Points to other AAS objects. +- **GlobalReference** → Points to external definitions. + +--- + +## 6. JSON Representation +AAS data is commonly exchanged in JSON. + +**Characteristics:** +- Deeply nested objects +- Arrays of submodel elements +- Semantic ID references +- Structured data types + + +--- + +## 7. Coding Guidelines +When generating **C# or API code** for AAS: +- Use strongly typed models (avoid dynamic objects). +- Respect hierarchical structure: + - AssetAdministrationShell → Submodel → SubmodelElement +- Handle nested collections correctly. +- Use `System.Text.Json` for serialization. +- Preserve semantics of `semanticId` and `idShort`. + +--- + +## 8. API Design Guidelines +For APIs handling AAS data: +- Use RESTful endpoints. +- Return structured JSON consistent with AAS. +- Support retrieval of: + - AAS + - Submodels + - Submodel elements +- Validate identifiers and semantic references. + +--- + +## 9. References +For deeper study of AAS specifications: +- [Details of the Asset Administration Shell Part 1 V3.0RC02 (PDF)](https://industrialdigitaltwin.org/wp-content/uploads/2022/06/DetailsOfTheAssetAdministrationShell_Part1_V3.0RC02_Final1.pdf) +- [IDTA Specification v3.1.1 – Submodel Elements](https://industrialdigitaltwin.io/aas-specifications/IDTA-01001/v3.1.1/spec-metamodel/submodel-elements.html#entity-attributes) \ No newline at end of file diff --git a/.github/skills/csharp-async/SKILL.md b/.github/skills/csharp-async/SKILL.md new file mode 100644 index 0000000..4dbe78b --- /dev/null +++ b/.github/skills/csharp-async/SKILL.md @@ -0,0 +1,49 @@ +--- +name: csharp-async +description: 'Get best practices for C# async programming' +--- + +# C# Async Programming Best Practices + +Your goal is to help me follow best practices for asynchronous programming in C#. + +## Naming Conventions + +- Use the 'Async' suffix for all async methods +- Match method names with their synchronous counterparts when applicable (e.g., `GetDataAsync()` for `GetData()`) + +## Return Types + +- Return `Task` when the method returns a value +- Return `Task` when the method doesn't return a value +- Consider `ValueTask` for high-performance scenarios to reduce allocations +- Avoid returning `void` for async methods except for event handlers + +## Exception Handling + +- Use try/catch blocks around await expressions +- Avoid swallowing exceptions in async methods +- Use `ConfigureAwait(false)` when appropriate to prevent deadlocks in library code +- Propagate exceptions with `Task.FromException()` instead of throwing in async Task returning methods + +## Performance + +- Use `Task.WhenAll()` for parallel execution of multiple tasks +- Use `Task.WhenAny()` for implementing timeouts or taking the first completed task +- Avoid unnecessary async/await when simply passing through task results +- Consider cancellation tokens for long-running operations + +## Common Pitfalls + +- Never use `.Wait()`, `.Result`, or `.GetAwaiter().GetResult()` in async code +- Avoid mixing blocking and async code +- Don't create async void methods (except for event handlers) +- Always await Task-returning methods + +## Implementation Patterns + +- Implement the async command pattern for long-running operations +- Use async streams (IAsyncEnumerable) for processing sequences asynchronously +- Consider the task-based asynchronous pattern (TAP) for public APIs + +When reviewing my C# code, identify these issues and suggest improvements that follow these best practices. diff --git a/.github/skills/csharp-xunit/SKILL.md b/.github/skills/csharp-xunit/SKILL.md new file mode 100644 index 0000000..4347c5a --- /dev/null +++ b/.github/skills/csharp-xunit/SKILL.md @@ -0,0 +1,68 @@ +--- +name: csharp-xunit +description: 'Get best practices for XUnit unit testing, including data-driven tests' +--- + +# XUnit Best Practices + +Your goal is to help me write effective unit tests with XUnit, covering both standard and data-driven testing approaches. + +## Project Setup + +- Use a separate test project with naming convention `[ProjectName].Tests` +- Reference Microsoft.NET.Test.Sdk, xunit, and xunit.runner.visualstudio packages +- Create test classes that match the classes being tested (e.g., `CalculatorTests` for `Calculator`) +- Use .NET SDK test commands: `dotnet test` for running tests + +## Test Structure + +- No test class attributes required (unlike MSTest/NUnit) +- Use fact-based tests with `[Fact]` attribute for simple tests +- Follow the Arrange-Act-Assert (AAA) pattern +- Name tests using the pattern `MethodName_Scenario_ExpectedBehavior` +- Use constructor for setup and `IDisposable.Dispose()` for teardown +- Use `IClassFixture` for shared context between tests in a class +- Use `ICollectionFixture` for shared context between multiple test classes + +## Standard Tests + +- Keep tests focused on a single behavior +- Avoid testing multiple behaviors in one test method +- Use clear assertions that express intent +- Include only the assertions needed to verify the test case +- Make tests independent and idempotent (can run in any order) +- Avoid test interdependencies + +## Data-Driven Tests + +- Use `[Theory]` combined with data source attributes +- Use `[InlineData]` for inline test data +- Use `[MemberData]` for method-based test data +- Use `[ClassData]` for class-based test data +- Create custom data attributes by implementing `DataAttribute` +- Use meaningful parameter names in data-driven tests + +## Assertions + +- Use `Assert.Equal` for value equality +- Use `Assert.Same` for reference equality +- Use `Assert.True`/`Assert.False` for boolean conditions +- Use `Assert.Contains`/`Assert.DoesNotContain` for collections +- Use `Assert.Matches`/`Assert.DoesNotMatch` for regex pattern matching +- Use `Assert.Throws` or `await Assert.ThrowsAsync` to test exceptions +- Use fluent assertions library for more readable assertions + +## Mocking and Isolation + +- Consider using Moq or NSubstitute alongside XUnit +- Mock dependencies to isolate units under test +- Use interfaces to facilitate mocking +- Consider using a DI container for complex test setups + +## Test Organization + +- Group tests by feature or component +- Use `[Trait("Category", "CategoryName")]` for categorization +- Use collection fixtures to group tests with shared dependencies +- Consider output helpers (`ITestOutputHelper`) for test diagnostics +- Skip tests conditionally with `Skip = "reason"` in fact/theory attributes