Skip to content
Draft
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
280 changes: 280 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
# Copilot Instructions for Slack.NetStandard

## Project Overview

**Slack.NetStandard** is a comprehensive .NET Standard 2.0 NuGet package providing a full-featured Slack API client for C#/.NET. It covers:

- **Web API** – 50+ method groups (Chat, Conversations, Users, Admin, Files, Views, Workflows, etc.)
- **Events API** – 50+ real-time event types for Slack app events
- **Interactive Components** – Slash commands, block actions, modals, shortcuts
- **Socket Mode** – WebSocket-based Slack communication
- **Message Building** – Rich message formatting with Blocks, Elements, and Rich Text
- **Request Verification** – HMAC-SHA256 cryptographic verification of incoming Slack requests
- **OAuth 2.0** – Authorization code exchange and OAuth flows

Current version: **11.1.0** | License: MIT | NuGet: `Slack.NetStandard`

---

## Repository Structure

```
Slack.NetStandard/ # Main library (920 C# files, ~22k LOC)
WebApi/ # API method group implementations (~50 groups)
Chat/ # e.g. ChatApi.cs, IChatApi.cs, PostMessageRequest.cs
Conversations/
Users/
Admin/ # Nested sub-groups (Admin.Users, Admin.Channels, …)
...
EventsApi/
CallbackEvents/ # Individual event types (AppMention, MessageReceived, …)
Interaction/ # Interactive payload types
Messages/
Blocks/ # IMessageBlock implementations
Elements/ # IMessageElement implementations
Objects/ # TextObject, ConfirmationObject, etc.
JsonConverters/ # Custom Newtonsoft.Json converters
RequestVerification/ # Request signature verification
TextParsing/ # Mention/link/channel text parsing
SocketMode/ # Socket Mode envelope types
Slack.NetStandard.Tests/ # Test suite (60 C# files, ~6k LOC)
Examples/ # 200+ real Slack JSON response fixtures
Utility.cs # Shared test helpers
WebApiTests_*.cs # Per-group API tests
EventsApiTests.cs
BlockTests.cs
InteractionTests.cs
SocketTests.cs
...
Slack.NetStandard.Analyzers/ # Custom Roslyn analyzers (bundled as private assets)
.github/workflows/
main.yml # Build → Test → Deploy to NuGet.org + GitHub Packages
beta.yml # Same pipeline targeting beta branch
```

---

## Build, Test, and Lint

```bash
# Restore dependencies
dotnet restore

# Build (warnings are errors in both Debug and Release)
dotnet build -c Release

# Run all tests
dotnet test

# Run tests with verbose output
dotnet test -v detailed
```

- **Target frameworks**: Library → .NET Standard 2.0; Tests → .NET 8.0
- `TreatWarningsAsErrors=True` is enforced in all configurations – fix every warning.
- The main library auto-generates a `.nupkg` on every build (`GeneratePackageOnBuild=true`).
- No `.editorconfig` or StyleCop; code quality is enforced via the bundled Roslyn analyzers in `Slack.NetStandard.Analyzers/`.
- CI skips when commit message includes `***NO_CI***`, `[ci skip]`, or `[skip ci]`.

---

## Core Architecture

### Entry Point – `SlackWebApiClient`

The primary public class. Implements `ISlackApiClient` and `IWebApiClient`. It:
- Accepts a Bearer token string **or** a custom `HttpClient`.
- Lazy-initialises each API group on first access.
- Delegates HTTP transport to its own `IWebApiClient` methods.

```csharp
var client = new SlackWebApiClient("xoxb-token");
var result = await client.Chat.Post(new PostMessageRequest { Channel = "C123", Text = "Hi" });
```

### Facade + Lazy Initialization Pattern

Every API group is exposed as a property on `SlackWebApiClient`, backed by a nullable field:

```csharp
private IChatApi _chat;
public IChatApi Chat => _chat ??= new ChatApi(this);
```

All API implementation classes (`ChatApi`, `ConversationsApi`, …) are **`internal`**. They are accessed only through their public interfaces.

### HTTP Transport – `IWebApiClient`

Four call patterns used throughout:

| Method | Usage |
|---|---|
| `MakeJsonCall<TReq, TRes>(method, request)` | JSON POST (most common) |
| `MakeUrlEncodedCall<TRes>(method, dict)` | Form-encoded POST |
| `MakeGetCall<TReq, TRes>(method, request)` | GET with query string |
| `MakeMultiPartCall<TRes>(method, data, files)` | Multipart / file upload |

### Request / Response Pattern

- **Request**: POCO with `[JsonProperty("snake_case")]` attributes; optional properties use `NullValueHandling = NullValueHandling.Ignore`.
- **Response**: Inherits from `WebApiResponseBase` (provides `OK`, `Error`, `Warning`, `Errors`, `RetryAfter`). Pagination uses `ResponseMetadata` with a `NextCursor`.

---

## Key Code Conventions

### Naming

| Element | Convention | Example |
|---|---|---|
| Interfaces | `I` prefix | `ISlackApiClient`, `IChatApi` |
| Classes | PascalCase | `SlackWebApiClient`, `PostMessageRequest` |
| Methods | PascalCase | `Post()`, `Delete()`, `AccessLogs()` |
| Properties | PascalCase | `Text`, `BlockId`, `ResponseUrl` |
| Private fields | `_camelCase` | `_client`, `_conversations` |
| JSON keys | snake_case via attribute | `[JsonProperty("block_id")]` |

### Serialization

- **Newtonsoft.Json** (JSON.NET) throughout – do **not** use `System.Text.Json`.
- Always use `[JsonProperty("name")]` for every serialized property.
- Use `NullValueHandling = NullValueHandling.Ignore` for all optional properties.
- Custom converters live in `JsonConverters/` (e.g., `EventTypeConverter`, `BlockTypeConverter`).
- `[JsonExtensionData]` captures unknown Slack API fields to avoid deserialization failures.

### One class per file

Keep one primary class per `.cs` file. Name the file after the class.

---

## Patterns for Adding New Features

### Adding a New API Endpoint to an Existing Group

1. **Request & Response classes** in `WebApi/<GroupName>/`:

```csharp
public class MyNewRequest
{
[JsonProperty("channel")]
public string Channel { get; set; }

[JsonProperty("optional_param", NullValueHandling = NullValueHandling.Ignore)]
public string OptionalParam { get; set; }
}

public class MyNewResponse : WebApiResponse
{
[JsonProperty("result", NullValueHandling = NullValueHandling.Ignore)]
public string Result { get; set; }
}
```

2. **Interface** – add method signature to `I<GroupName>Api.cs`:

```csharp
Task<MyNewResponse> NewMethod(MyNewRequest request);
```

3. **Implementation** – add method to the `internal` `<GroupName>Api.cs`:

```csharp
public Task<MyNewResponse> NewMethod(MyNewRequest request) =>
_client.MakeJsonCall<MyNewRequest, MyNewResponse>("group.newMethod", request);
```

4. **Test** in `WebApiTests_<GroupName>.cs`:

```csharp
[Fact]
public async Task GroupName_NewMethod()
{
await Utility.AssertWebApi(
c => c.GroupName.NewMethod(new MyNewRequest { Channel = "C123" }),
"group.newMethod",
"Web_GroupName_NewMethod.json",
jobject => Assert.Equal("C123", jobject.Value<string>("channel")));
}
```

5. **Fixture** – add `Examples/Web_GroupName_NewMethod.json` with a representative Slack API response.

### Adding a New API Group

Follow all five steps above, plus:

6. Add a lazy-init property to `SlackWebApiClient`:

```csharp
private INewGroupApi _newGroup;
public INewGroupApi NewGroup => _newGroup ??= new NewGroupApi(this);
```

7. Add the property to the `ISlackApiClient` interface.

### Adding a New Event Type

1. Create class in `EventsApi/CallbackEvents/`:

```csharp
public class MyNewEvent : CallbackEvent
{
public override string Type => "my_new_event";

[JsonProperty("property", NullValueHandling = NullValueHandling.Ignore)]
public string Property { get; set; }
}
```

2. Register the type string in `EventTypeConverter` (in `JsonConverters/`).

3. Add a test to `EventsApiTests.cs` and a fixture `Examples/Events_MyNewEvent.json`.

### Adding a New Message Block

1. Create class in `Messages/Blocks/` implementing `IMessageBlock`.
2. Add the type string to `BlockTypeConverter`.
3. Add a round-trip test to `BlockTests.cs` and fixture `Examples/Blocks_MyBlock.json`.

---

## Test Helpers

`Utility.cs` provides:

| Helper | Purpose |
|---|---|
| `Utility.AssertWebApi(call, method, fixture, checks)` | Verifies a Web API call serialises/deserialises correctly against a JSON fixture |
| `Utility.AssertSubType<TBase, TDerived>(fixture)` | Verifies polymorphic JSON deserialises to the correct derived type |
| `Utility.CompareJson(obj, fixture)` | Serialises `obj` and asserts it matches the fixture JSON |

All fixture files live in `Slack.NetStandard.Tests/Examples/`. Naming conventions:

| Prefix | Area |
|---|---|
| `Web_` | Web API requests/responses |
| `Events_` | Events API payloads |
| `Blocks_` | Message blocks |
| `Cells_` | SlackLists cell types |
| `Socket_` | Socket Mode envelopes |
| `WorkObjects_` | Workflow objects |

---

## CI/CD

- **`main` branch**: build → test → push to NuGet.org (`NUGET_KEY` secret) and GitHub Packages.
- **`beta` branch**: same pipeline with a `-beta` suffix package.
- Deployments only happen on a direct push to `main`/`beta`; PRs run build + test only.

---

## Common Pitfalls & Known Workarounds

- **Treat warnings as errors**: Any new compiler warning will break the build. Always resolve analyzer and compiler warnings before committing.
- **Use `NullValueHandling.Ignore`** on every optional JSON property or Slack's strict field presence rules may cause unexpected serialisation behaviour.
- **Do not use `System.Text.Json`**: The entire library depends on Newtonsoft.Json conventions and custom converters.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi !
Why not to use System.Text.Json ?

Cheers

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The converters and attributes in this library are based on Newtonsoft, if the agent built anything using System.Text.Json then the output wouldn't work with the rest of the library and the serialized/deserialised output would be incorrect.

Originally this was because the early versions of System.Text.Json didn't support polymorphism and their interface support was poor, so it was necessary for things like the block converters. Now it's a large piece of work for little reward.

Copilot did a good first run of this, I need the rules I've created in Roslyn to be taken into account, and some of the response patterns to be written up, then this would be a good starting context for an agent to take the Slack update from the changelog and attempt a pull request which could then be tweaked - reducing overall time to stay as up to date as possible.

- **Internal API classes**: Never make `*Api` implementation classes `public`. They must remain `internal` and only be accessible via their `I*Api` interface.
- **Polymorphic event/block deserialization**: Adding a new event or block type without registering it in the corresponding `*TypeConverter` will cause it to silently deserialise as `null` or the base type.
- **Admin API nesting**: The `Admin` group has sub-groups (`Admin.Users`, `Admin.Apps`, `Admin.Conversations`, …). Follow the same nested lazy-init pattern used there when adding further sub-groups.