Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cursor/rules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Cursor (optional)

**Cursor** users: start at **[AGENTS.md](../../AGENTS.md)**. All conventions live in **`skills/*/SKILL.md`**.

This folder only points contributors to **`AGENTS.md`** so editor-specific config does not duplicate the canonical docs.
49 changes: 49 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Contentstack .NET SDK – Agent guide

**Universal entry point** for contributors and AI agents. Each skill is documented in **`skills/*/SKILL.md`** (YAML frontmatter for agent discovery where applicable).

## What this repo is

| Field | Detail |
|-------|--------|
| **Name:** | [contentstack-dotnet](https://github.com/contentstack/contentstack-dotnet) |
| **Purpose:** | .NET SDK for Contentstack’s Content Delivery API (CDA)—fetch entries, assets, and run queries from .NET apps. |
| **Out of scope (if any):** | Do not bypass the SDK HTTP layer with ad-hoc `HttpClient` usage; all requests go through `HttpRequestHandler` (see `skills/sdk-core-patterns/SKILL.md`). |

## Tech stack (at a glance)

| Area | Details |
|------|---------|
| Language | C#; multi-targeting includes `netstandard2.0`, `net47`, `net472` (see project files under `Contentstack.Core/`). |
| Build | .NET SDK — solution [`Contentstack.Net.sln`](Contentstack.Net.sln); packages [`Contentstack.Core/`](Contentstack.Core/) (Delivery SDK), [`Contentstack.AspNetCore/`](Contentstack.AspNetCore/) (DI extensions). |
| Tests | xUnit; unit tests in [`Contentstack.Core.Unit.Tests/`](Contentstack.Core.Unit.Tests/) (no credentials); integration tests in [`Contentstack.Core.Tests/`](Contentstack.Core.Tests/) (requires `app.config` / API credentials). |
| Lint / coverage | No dedicated repo-wide lint/format CLI in CI. Security/static analysis: [CodeQL workflow](.github/workflows/codeql-analysis.yml). |
| Other | JSON: Newtonsoft.Json; package version: single source in [`Directory.Build.props`](Directory.Build.props). |

## Commands (quick reference)

| Command type | Command |
|--------------|---------|
| Build | `dotnet build Contentstack.Net.sln` |
| Test (unit) | `dotnet test Contentstack.Core.Unit.Tests/Contentstack.Core.Unit.Tests.csproj` |
| Test (integration) | `dotnet test Contentstack.Core.Tests/Contentstack.Core.Tests.csproj` (configure credentials locally) |

CI: [`.github/workflows/unit-test.yml`](.github/workflows/unit-test.yml) restores, builds, and runs unit tests on Windows (.NET 7). Other workflows include [NuGet publish](.github/workflows/nuget-publish.yml), [branch checks](.github/workflows/check-branch.yml), [CodeQL](.github/workflows/codeql-analysis.yml), policy/SCA scans.

## Where the documentation lives: skills

| Skill | Path | What it covers |
|-------|------|----------------|
| Dev workflow | [`skills/dev-workflow/SKILL.md`](skills/dev-workflow/SKILL.md) | Solution layout, build/test commands, versioning, CI entry points. |
| SDK core patterns | [`skills/sdk-core-patterns/SKILL.md`](skills/sdk-core-patterns/SKILL.md) | Architecture, `ContentstackClient`, HTTP layer, DI, plugins. |
| Query building | [`skills/query-building/SKILL.md`](skills/query-building/SKILL.md) | Fluent query API, operators, pagination, sync, taxonomy. |
| Models and serialization | [`skills/models-and-serialization/SKILL.md`](skills/models-and-serialization/SKILL.md) | Entry/Asset models, JSON converters, collections. |
| Error handling | [`skills/error-handling/SKILL.md`](skills/error-handling/SKILL.md) | Exception hierarchy, `ErrorMessages`, API error parsing. |
| Testing | [`skills/testing/SKILL.md`](skills/testing/SKILL.md) | Unit vs integration tests, AutoFixture, `IntegrationTestBase`. |
| Code review | [`skills/code-review/SKILL.md`](skills/code-review/SKILL.md) | PR checklist for this SDK. |

An index with “when to use” hints is in [`skills/README.md`](skills/README.md).

## Using Cursor (optional)

If you use **Cursor**, [`.cursor/rules/README.md`](.cursor/rules/README.md) only points to **`AGENTS.md`**—the same conventions as for everyone else. Canonical guidance remains in **`skills/*/SKILL.md`**.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

.NET SDK for Contentstack's Content Delivery API

Contributor and agent conventions: see **[AGENTS.md](AGENTS.md)**.

## Getting Started

This guide will help you get started with our .NET SDK to build apps powered by Contentstack.
Expand Down
21 changes: 21 additions & 0 deletions skills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Skills – Contentstack .NET SDK

Source of truth for detailed guidance. Read **[AGENTS.md](../AGENTS.md)** first, then open the skill that matches your task.

## When to use which skill

| Skill folder | Use when |
|--------------|----------|
| `dev-workflow` | Building the solution, versioning, CI workflows, onboarding, PR prep. |
| `sdk-core-patterns` | Architecture, `ContentstackClient`, HTTP layer, DI, plugins, request flow. |
| `query-building` | Query operators, fluent API, pagination, sync, taxonomy, `Query.cs`. |
| `models-and-serialization` | Entry/Asset models, JSON converters, `ContentstackCollection`, serialization. |
| `error-handling` | Exceptions, `ErrorMessages`, parsing API errors. |
| `testing` | Writing or debugging unit/integration tests, coverage, test layout. |
| `code-review` | Reviewing a PR against SDK-specific checklist. |

Each folder contains **`SKILL.md`** with YAML frontmatter (`name`, `description`) for agent discovery.

### Cursor

In Cursor, you can also reference a skill in chat with `@skills/<folder-name>` (for example `@skills/testing`).
223 changes: 223 additions & 0 deletions skills/code-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
---
name: code-review
description: SDK-specific PR review checklist for the Contentstack .NET SDK — covers breaking changes, HTTP layer correctness, exception handling, serialization, fluent API patterns, configuration, test coverage, multi-targeting, plugin lifecycle, and internal visibility. Use when reviewing pull requests, examining code changes, or performing code quality assessments on this SDK.
---

# Code Review

## When to use

- Reviewing a PR or diff against SDK conventions.
- Self-review before opening a PR.

## Severity levels

- **Critical** — Must fix before merge (correctness, breaking changes, security)
- **Important** — Should fix (maintainability, SDK patterns, consistency)
- **Suggestion** — Consider improving (style, optimization)

The sections below provide category-by-category checklists (breaking changes, HTTP, exceptions, serialization, fluent API, config, tests, multi-targeting, plugins, visibility) and a short red-flag list for quick scanning.


## Code Review Checklist

### Breaking Changes Checklist

```markdown
## Breaking Changes Review
- [ ] No public method signatures removed or changed without [Obsolete] deprecation
- [ ] No [JsonProperty] values changed (would break consumer deserialization silently)
- [ ] No ContentstackOptions public property removed
- [ ] New required options have defaults (don't break existing consumers who don't set them)
- [ ] No namespace renames without backward-compatible type aliases
- [ ] No IContentstackPlugin interface signature changed
- [ ] Version bump planned if breaking change is intentional (Directory.Build.props)
```

### HTTP Layer Checklist

```markdown
## HTTP Layer Review
- [ ] All HTTP calls route through HttpRequestHandler.ProcessRequest
- [ ] No HttpClient instantiation anywhere in the PR
- [ ] New query params added to UrlQueries dict (not directly to URL string)
- [ ] New field-level filters added to QueryValueJson dict
- [ ] New headers added via Headers parameter to ProcessRequest
- [ ] Branch header uses "branch" key, passed as separate Branch parameter
- [ ] No hardcoded URLs — BaseUrl comes from Config.BaseUrl
- [ ] Live preview URL resolved via Config.getBaseUrl() — not hardcoded
- [ ] ProcessRequest result (string JSON) parsed, not further HTTP calls made
```

### Exception Handling Checklist

```markdown
## Exception Handling Review
- [ ] Domain-specific exception type used (QueryFilterException, AssetException, etc.)
- [ ] No bare `throw new Exception(...)` or `throw new ContentstackException(...)`
- [ ] All message strings sourced from ErrorMessages.cs constants
- [ ] No string literals in throw statements
- [ ] GetContentstackError(ex) called when catching WebException from HTTP calls
- [ ] ErrorCode, StatusCode, Errors preserved when re-wrapping exceptions
- [ ] New domain area has new exception class with factory methods
- [ ] New error messages added to correct section in ErrorMessages.cs
- [ ] FormatExceptionDetails(innerEx) used in ProcessingError factory methods
```

### Serialization Checklist

```markdown
## Serialization Review
- [ ] All public properties mapping CDA JSON fields have [JsonProperty("snake_case")]
- [ ] No reliance on default Newtonsoft.Json camelCase or PascalCase matching
- [ ] Custom deserialization uses [CSJsonConverter] + JsonConverter subclass
- [ ] JsonConverter placed in Contentstack.Core/Internals/ (internal class)
- [ ] No System.Text.Json usage
- [ ] No JsonConvert.DeserializeObject with hardcoded type outside of converter
- [ ] ContentstackCollection<T> used for list responses (not List<T> directly)
- [ ] "entries" token used for entry collection, "assets" for asset collection
```

### Fluent API Checklist

```markdown
## Fluent API Review
- [ ] Every Query filter/operator method returns `return this;`
- [ ] Null key validated at start of method → QueryFilterException.Create()
- [ ] Empty string key validated → QueryFilterException.Create()
- [ ] Operator value stored in QueryValueJson[key][$operator] nested dict
- [ ] URL-level params stored in UrlQueries[key]
- [ ] Method name follows verb+noun pattern (GreaterThan, ContainedIn, NotExists)
- [ ] No mutation of QueryValueJson or UrlQueries outside of the Query class itself
- [ ] And()/Or() accept Query[] (not raw dictionaries)
```

### Configuration Checklist

```markdown
## Configuration Review
- [ ] New options added to ContentstackOptions (public class), not Config (internal)
- [ ] New property has XML <summary> doc comment
- [ ] Default value set in ContentstackOptions() constructor or property initializer
- [ ] ContentstackClient constructor reads new option and passes to Config
- [ ] Config never exposed as public property
- [ ] New option tested in ContentstackOptionsUnitTests.cs
- [ ] ASP.NET Core binding works (IOptions<ContentstackOptions> path verified)
```

### Test Coverage Checklist

```markdown
## Test Coverage Review
- [ ] Unit test for each new public Query method (QueryValueJson assertion via reflection)
- [ ] Unit test for null key input → QueryFilterException
- [ ] Unit test for empty key input → QueryFilterException
- [ ] Unit test for fluent return (Assert.Equal(query, result))
- [ ] Integration test file in Integration/{FeatureName}Tests/ subfolder
- [ ] Integration test class extends IntegrationTestBase
- [ ] Integration test constructor takes ITestOutputHelper output
- [ ] CreateClient() used (not manual ContentstackClient construction)
- [ ] LogArrange/LogAct/LogAssert used in correct order
- [ ] TestAssert.* used (not raw Assert.*)
- [ ] [Fact(DisplayName = "FeatureArea - Component Description")] present
- [ ] Happy path test (valid params → expected response)
- [ ] Error path test (invalid params or not found → expected exception)
```

### Multi-Targeting Checklist

```markdown
## Multi-Targeting Review
- [ ] No HttpClient (netstandard2.0 HttpClient has behavioural differences from net4x)
- [ ] No System.Text.Json (not available without separate package in netstandard2.0)
- [ ] No record types (C# 9, requires LangVersion setting for net47/net472)
- [ ] No default interface implementations (C# 8, may affect net47)
- [ ] No nullable reference types without #nullable enable guard
- [ ] No top-level statements (not applicable to library projects but worth checking)
- [ ] Tested compile against netstandard2.0 target (or verified via CI)
```

### Plugin Lifecycle Checklist

```markdown
## Plugin Lifecycle Review
- [ ] New feature that makes HTTP calls uses HttpRequestHandler (plugins run automatically)
- [ ] No WebRequest.Create() called directly in new model classes
- [ ] IContentstackPlugin interface not modified (breaking for all plugin consumers)
- [ ] RequestLoggingPlugin still works with any new request/response changes
- [ ] Plugin.OnRequest receives HttpWebRequest before send
- [ ] Plugin.OnResponse receives response string (can mutate/inspect)
```

### Internal Visibility Checklist

```markdown
## Internal Visibility Review
- [ ] New utility/helper classes in Internals/ are marked `internal`
- [ ] New model types intended for consumers are in Models/ and `public`
- [ ] New configuration types are in Configuration/ and `public`
- [ ] No public exposure of Config, HttpRequestHandler, or VersionUtility
- [ ] InternalsVisibleTo not modified (already covers both test projects)
- [ ] New internal methods accessible in unit tests without changes
```

### Common Issues Found in Past PRs

#### Silent Deserialization Failures
`[JsonProperty]` omitted → field is always null at runtime, no exception. Verify all properties that map CDA JSON fields.

#### Exception Message in Throw
```csharp
// Bad
throw new QueryFilterException("Please provide valid params.");

// Good
throw QueryFilterException.Create(innerEx);
// or
throw new QueryFilterException(ErrorMessages.QueryFilterError);
```

#### Hardcoded Environment
```csharp
// Bad — breaks for consumers with different environments
mainJson["environment"] = "production";

// Correct — already done in Exec()
mainJson["environment"] = ContentTypeInstance.StackInstance.Config.Environment;
```

#### Returning void from Query Method
```csharp
// Bad — breaks fluent chaining
public void SetMyParam(string value) { UrlQueries["my_param"] = value; }

// Good
public Query SetMyParam(string value) { UrlQueries["my_param"] = value; return this; }
```

#### Dictionary Not Initialized for QueryValueJson Entry
```csharp
// Bad — throws KeyNotFoundException or InvalidCastException
((Dictionary<string, object>)QueryValueJson[key])["$op"] = value;

// Good — guard with ContainsKey
if (!QueryValueJson.ContainsKey(key))
QueryValueJson[key] = new Dictionary<string, object>();
((Dictionary<string, object>)QueryValueJson[key])["$op"] = value;
```

## SDK-specific red flags

Quick scan for anti-patterns in PRs:

```
❌ new HttpClient() — use HttpRequestHandler
❌ throw new Exception("message") — use typed ContentstackException subclass
❌ "hardcoded_field_name" — use [JsonProperty] or ErrorMessages constant
❌ public Config GetConfig() — Config is internal by design
❌ return void — Query methods return Query (fluent)
❌ [JsonProperty] omitted — CDA uses snake_case; PascalCase won't deserialize
❌ <Version> in .csproj — use Directory.Build.props
```

For full category-by-category review, see **Code review checklist** above.
Loading
Loading