Skip to content

Commit 82397c9

Browse files
docs: update contributing guide for .NET 10 SDK and setup instructions
1 parent cf8ede0 commit 82397c9

1 file changed

Lines changed: 83 additions & 296 deletions

File tree

docs/contributing.md

Lines changed: 83 additions & 296 deletions
Original file line numberDiff line numberDiff line change
@@ -1,296 +1,83 @@
1-
# Contributing Guide
2-
3-
This guide covers development practices, testing patterns, and contribution workflow for JsonApiToolkit.
4-
5-
## Development Setup
6-
7-
### Prerequisites
8-
9-
- .NET 9.0 SDK
10-
- Git
11-
- An IDE (VS Code, Rider, or Visual Studio)
12-
13-
### Building
14-
15-
```bash
16-
# Restore dependencies
17-
dotnet restore
18-
19-
# Build
20-
dotnet build --configuration Release
21-
22-
# Run tests
23-
dotnet test --configuration Release
24-
25-
# Format code
26-
dotnet csharpier format .
27-
```
28-
29-
## Testing Patterns
30-
31-
### Test Organization
32-
33-
Tests are organized by component in the `JsonApiToolkit.Tests` project:
34-
35-
```
36-
JsonApiToolkit.Tests/
37-
├── Configuration/ # JsonApiOptions, QueryComplexityAnalyzer
38-
├── Controllers/ # JsonApiController behavior
39-
├── Extensions/
40-
│ ├── Filtering/ # FilterHandler, FilterExpressionBuilder
41-
│ ├── Pagination/ # PaginationHandler
42-
│ ├── Sorting/ # SortingHandler
43-
│ └── QueryHelpersTests.cs
44-
├── Filters/ # JsonApiExceptionFilter
45-
├── Integration/ # Full HTTP pipeline tests
46-
├── Mapping/ # JsonApiMapper, InclusionMapper, EntityMapper
47-
├── Models/ # Test entities and model tests
48-
├── Parsing/ # JsonApiQueryParser
49-
├── Security/ # DoS protection, bypass attempts
50-
└── Validation/ # IncludeValidator, AllowedIncludes
51-
```
52-
53-
### Test Naming Convention
54-
55-
Follow the pattern: `MethodName_Scenario_ExpectedBehavior`
56-
57-
```csharp
58-
// Good examples
59-
[Fact]
60-
public void ApplyPagination_WithPageSizeZero_ClampsToOne() { }
61-
62-
[Fact]
63-
public void ConvertToPropertyType_WithInvalidGuid_ThrowsFormatException() { }
64-
65-
[Fact]
66-
public async Task GetArticles_FilterSortPaginate_AppliesAllOperationsAsync() { }
67-
```
68-
69-
### Test Categories
70-
71-
#### 1. Unit Tests
72-
73-
Test individual methods in isolation:
74-
75-
```csharp
76-
[Fact]
77-
public void CountFilters_WithNestedGroups_CountsAllFilters()
78-
{
79-
var group = new FilterGroup
80-
{
81-
Filters = [new() { Field = "a", Value = "1" }],
82-
Groups = [new FilterGroup { Filters = [new() { Field = "b", Value = "2" }] }],
83-
};
84-
85-
int count = QueryComplexityAnalyzer.CountFilters(group);
86-
87-
Assert.Equal(2, count);
88-
}
89-
```
90-
91-
#### 2. Integration Tests
92-
93-
Test the full HTTP pipeline with TestServer:
94-
95-
```csharp
96-
[Fact]
97-
public async Task GetArticles_WithPagination_ReturnsCorrectPageAsync()
98-
{
99-
var response = await _client.GetAsync("/api/articles?page[number]=1&page[size]=2");
100-
101-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
102-
103-
var document = JsonSerializer.Deserialize<JsonApiCollectionDocument<ResourceObject>>(
104-
await response.Content.ReadAsStringAsync(),
105-
_jsonOptions
106-
);
107-
108-
Assert.Equal(2, document?.Data?.Count());
109-
}
110-
```
111-
112-
#### 3. Boundary Tests
113-
114-
Test edge cases and limits:
115-
116-
```csharp
117-
[Theory]
118-
[InlineData(0, 1)] // Zero clamps to 1
119-
[InlineData(-1, 1)] // Negative clamps to 1
120-
[InlineData(int.MaxValue, 100)] // Exceeds max, clamps to max
121-
public void ApplyPagination_WithBoundaryPageSize_ClampsCorrectly(
122-
int inputSize,
123-
int expectedSize)
124-
{
125-
// Test implementation
126-
}
127-
```
128-
129-
#### 4. Error Condition Tests
130-
131-
Test that errors are handled correctly:
132-
133-
```csharp
134-
[Fact]
135-
public void ConvertToPropertyType_WithInvalidInt_ThrowsFormatException()
136-
{
137-
var exception = Assert.Throws<FormatException>(() =>
138-
QueryHelpers.ConvertToPropertyType("not-a-number", typeof(int))
139-
);
140-
141-
Assert.Contains("Failed to convert filter value", exception.Message);
142-
}
143-
```
144-
145-
### Test Infrastructure
146-
147-
#### In-Memory Database
148-
149-
Use EF Core in-memory database for integration tests:
150-
151-
```csharp
152-
public class TestDbContext : DbContext
153-
{
154-
public DbSet<TestEntity> Entities { get; set; } = null!;
155-
156-
public TestDbContext(DbContextOptions<TestDbContext> options)
157-
: base(options) { }
158-
}
159-
160-
// In test setup
161-
services.AddDbContext<TestDbContext>(options =>
162-
options.UseInMemoryDatabase($"TestDb_{Guid.NewGuid()}")
163-
);
164-
```
165-
166-
#### Test Server Setup
167-
168-
```csharp
169-
_host = new HostBuilder()
170-
.ConfigureWebHost(webBuilder =>
171-
{
172-
webBuilder
173-
.UseTestServer()
174-
.ConfigureServices(services =>
175-
{
176-
services.AddDbContext<TestDbContext>(options =>
177-
options.UseInMemoryDatabase(databaseName)
178-
);
179-
services.AddControllers();
180-
services.AddJsonApiToolkit();
181-
})
182-
.Configure(app =>
183-
{
184-
app.UseRouting();
185-
app.UseEndpoints(endpoints => endpoints.MapControllers());
186-
187-
// Seed data
188-
using var scope = app.ApplicationServices.CreateScope();
189-
var context = scope.ServiceProvider.GetRequiredService<TestDbContext>();
190-
SeedTestData(context);
191-
});
192-
})
193-
.Build();
194-
```
195-
196-
### Coverage Requirements
197-
198-
Before merging to main:
199-
200-
- All public methods must have tests
201-
- All filter operators must have positive and negative tests
202-
- All query handlers must have boundary tests
203-
- Integration tests must cover the full query pipeline
204-
205-
## Code Style
206-
207-
### Formatting
208-
209-
All code is formatted with CSharpier. Run before committing:
210-
211-
```bash
212-
dotnet csharpier format .
213-
```
214-
215-
CI will fail if code is not formatted.
216-
217-
### Naming Conventions
218-
219-
| Element | Convention | Example |
220-
|---------|-----------|---------|
221-
| Classes | PascalCase | `FilterHandler` |
222-
| Interfaces | IPascalCase | `IFilterHandler` |
223-
| Methods | PascalCase | `ApplyPagination` |
224-
| Parameters | camelCase | `queryParameters` |
225-
| Private fields | _camelCase | `_logger` |
226-
| Constants | PascalCase | `DefaultPageSize` |
227-
228-
### Documentation
229-
230-
- Public APIs must have XML documentation
231-
- Include `<summary>`, `<param>`, and `<returns>` where applicable
232-
- Keep comments concise and meaningful
233-
234-
## Git Workflow
235-
236-
### Branching
237-
238-
Use conventional commit prefixes for branch names:
239-
240-
| Type | Branch | Example |
241-
|------|--------|---------|
242-
| Bug fix | `fix/` | `fix/pagination-zero-divide` |
243-
| Feature | `feat/` | `feat/sparse-fieldsets` |
244-
| Tests | `test/` | `test/security-tests` |
245-
| Docs | `docs/` | `docs/contributing-guide` |
246-
| Refactor | `refactor/` | `refactor/di-interfaces` |
247-
248-
### Commit Messages
249-
250-
Follow conventional commits:
251-
252-
```bash
253-
# Bug fixes
254-
git commit -m "fix: prevent division by zero in pagination"
255-
256-
# Features
257-
git commit -m "feat: add sparse fieldsets support"
258-
259-
# Tests
260-
git commit -m "test: add security tests for DoS protection"
261-
262-
# Breaking changes
263-
git commit -m "feat!: require DI in JsonApiController"
264-
```
265-
266-
### Pull Requests
267-
268-
1. Create a branch from `main`
269-
2. Make changes with conventional commits
270-
3. Ensure all tests pass: `dotnet test`
271-
4. Ensure code is formatted: `dotnet csharpier check .`
272-
5. Create PR with descriptive title and bullet-point description
273-
6. Wait for CI checks to pass
274-
7. Squash merge to main
275-
276-
## Release Process
277-
278-
Releases are managed by release-please. When PRs are merged to main:
279-
280-
1. release-please creates/updates a Release PR
281-
2. The Release PR accumulates changes based on conventional commits
282-
3. When ready, merge the Release PR
283-
4. This triggers a GitHub Release and NuGet publish
284-
285-
### Version Bumps
286-
287-
| Commit Prefix | Version Bump |
288-
|---------------|--------------|
289-
| `fix:` | Patch (1.0.x) |
290-
| `feat:` | Minor (1.x.0) |
291-
| `feat!:` or `fix!:` | Major (x.0.0) |
292-
| `docs:`, `test:`, `refactor:` | No bump |
293-
294-
## Questions?
295-
296-
Open an issue on GitHub: https://github.com/Intility/JsonApiToolkit/issues
1+
# Contributing Guide
2+
3+
## Prerequisites
4+
5+
- .NET 10 SDK (pinned in `global.json` to `10.0.201`)
6+
- `uv` for the docs site (`brew install uv` or `mise use uv`)
7+
8+
## Setup
9+
10+
```bash
11+
dotnet tool restore # csharpier + dotnet-api-docs
12+
dotnet restore
13+
```
14+
15+
> **Intility contributors:** `NuGet.config` currently resolves dependencies from `nuget.pkg.github.com/Intility`. Until the package moves to public NuGet, restore needs `NUGET_AUTH_TOKEN` set to a GitHub PAT with `read:packages`.
16+
17+
## Daily Commands
18+
19+
```bash
20+
dotnet build --configuration Release
21+
dotnet test --configuration Release
22+
dotnet csharpier format . # CI fails on unformatted code
23+
```
24+
25+
## Docs
26+
27+
The site is built with mkdocs (Material) and served from GitHub Pages.
28+
29+
```bash
30+
uv venv
31+
uv pip install -r docs/requirements.txt
32+
uv run mkdocs serve # http://127.0.0.1:8000, live reload
33+
uv run mkdocs build --strict # what CI runs; do this before pushing
34+
```
35+
36+
If you change C# XML doc comments, regenerate the API reference first:
37+
38+
```bash
39+
dotnet build JsonApiToolkit/JsonApiToolkit.csproj -c Release -o JsonApiToolkit/bin/docs
40+
dotnet tool run dotnet-api-docs -- --input JsonApiToolkit/bin/docs --output docs/api --strict
41+
```
42+
43+
## Tests
44+
45+
Tests live in `JsonApiToolkit.Tests/` and use xUnit + EF Core in-memory databases. Integration tests spin up a `TestServer` via `HostBuilder`; see existing tests in `Integration/` for the pattern.
46+
47+
Naming: `MethodName_Scenario_ExpectedBehavior` (e.g. `ApplyPagination_WithPageSizeZero_ClampsToOne`).
48+
49+
When adding behavior, add tests covering the happy path, boundaries, and error conditions.
50+
51+
## Commits
52+
53+
Conventional Commits, enforced indirectly by Release Please:
54+
55+
| Prefix | Bump | Notes |
56+
|---|---|---|
57+
| `fix:` | patch | |
58+
| `feat:` | minor | |
59+
| `feat!:` / `fix!:` | major | breaking change |
60+
| `perf:`, `refactor:`, `docs:`, `build:`, `ci:`, `test:`, `style:` | none | shows in changelog |
61+
| `chore:` | none | hidden from changelog |
62+
63+
Branch names: `feat/`, `fix/`, `refactor/`, `docs/`, `test/`, `chore/`, etc.
64+
65+
## Pull Requests
66+
67+
1. Branch from `main`.
68+
2. Format (`dotnet csharpier format .`) and run tests locally.
69+
3. Open a PR with a descriptive title and bullet summary.
70+
4. CI must pass `build-and-test` and `Docs: Build` (required status checks).
71+
5. Squash merge (the only merge method allowed on `main`).
72+
73+
## Releases
74+
75+
Handled by [Release Please](https://github.com/googleapis/release-please). Merging to `main` updates a release PR that accumulates changes. Merging the release PR cuts a GitHub Release, publishes to NuGet, and bumps the version in `JsonApiToolkit.csproj` and `mkdocs.yaml`.
76+
77+
## AI-Assisted Contributions
78+
79+
Project-specific guidance for AI tools lives in `AGENTS.md` at the repo root (with `CLAUDE.md` as a symlink). Update it there if you add conventions other contributors' tools should follow.
80+
81+
## Questions
82+
83+
Open an issue: <https://github.com/intility/Intility.JsonApiToolkit/issues>

0 commit comments

Comments
 (0)