Skip to content

Commit 3383d73

Browse files
committed
add copilot instructions for go-zulip project
1 parent 630d3e7 commit 3383d73

File tree

1 file changed

+266
-0
lines changed

1 file changed

+266
-0
lines changed

.github/copilot-instructions.md

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# AI Coding Agent Instructions for go-zulip
2+
3+
4+
## Project Overview
5+
6+
**go-zulip** is a Go API client for the Zulip group chat platform. The codebase consists of 100+ Go files organized into a modular service-based architecture. The project structure and tooling have been updated for improved maintainability and code quality.
7+
8+
9+
### Key Facts
10+
- **Language**: Go 1.25.2
11+
- **Scope**: Zulip REST API v1.0.0 client
12+
- **Testing**: Integration tests with live Zulip server, automated via GitHub Actions
13+
- **Linters/Formatters**: `golangci-lint`, `golangci-lint fmt`, enforced via pre-commit hooks
14+
15+
---
16+
17+
18+
## Directory Structure
19+
20+
```
21+
go.mod, go.sum, LICENSE, README.md
22+
internal/
23+
apiutils/ # API utility helpers
24+
clients/ # HTTP client implementations and helpers
25+
enum/ # Enum types
26+
strict_decoder/ # Strict JSON decoding utilities
27+
test_utils/ # Test helpers
28+
union/ # Union type marshaling logic
29+
scripts/ # Dev scripts
30+
zulip/
31+
*.go # Data models, API logic, and tests
32+
api/ # API domain service implementations
33+
client/ # Main client and statistics
34+
events/ # Event types and event handling
35+
.github/
36+
workflows/ # CI configuration (GitHub Actions)
37+
.pre-commit-config.yaml # Pre-commit hooks config
38+
```
39+
40+
---
41+
42+
## Architecture Patterns
43+
44+
### 1. **Service-Based Client Architecture**
45+
The codebase uses a **service-oriented architecture** where the main client (`client.Client`) aggregates multiple API domain services:
46+
- **Client**: `client.Client` interface in `zulip/client/client.go` aggregates multiple API domain interfaces
47+
- **Services**: Each API domain (authentication, channels, messages, etc.) has its own service implementation
48+
- **API Client**: Core HTTP client in `zulip/internal/api_client/APIClient` handles HTTP communication
49+
- **Why**: Clean separation of concerns, easy testing, and modular design
50+
51+
### 2. **Builder Pattern for Requests**
52+
Every API endpoint uses the **Builder Pattern** for fluent configuration:
53+
```go
54+
// Example from api/channels/api_channels.go
55+
resp, httpRes, err := client.APIChannels.AddDefaultChannel(ctx).
56+
ChannelID(123).
57+
Execute()
58+
```
59+
Structure:
60+
- Service method returns a `Request` struct (e.g., `AddDefaultChannelRequest`)
61+
- Setter methods (e.g., `.ChannelID()`) return the same request type for chaining
62+
- `.Execute()` finalizes and sends the request
63+
64+
### 3. **API Domain Organization**
65+
API functionality is organized by domain with clear separation:
66+
- `zulip/api/*/api_*.go` - API service implementations with request builders
67+
- `zulip/api/*/api_*_responses.go` - Response types for each domain
68+
- `zulip/*.go` - Data models (150+ enum and struct types)
69+
70+
---
71+
72+
73+
## CI, Testing, and Linters
74+
75+
### Continuous Integration (CI)
76+
- **GitHub Actions**: All pushes and PRs trigger workflows in `.github/workflows/`.
77+
- Linting: Runs `golangci-lint run` and `golangci-lint fmt` checks.
78+
- Testing: Runs `go test ./zulip/...` (requires Zulip server credentials for integration tests).
79+
- Pre-commit: Validates all pre-commit hooks pass.
80+
81+
### Pre-commit Hooks
82+
- Configured in `.pre-commit-config.yaml` (run `pre-commit install` to enable).
83+
- Enforces:
84+
- `golangci-lint` (static analysis, style, and bug checks)
85+
- `golangci-lint fmt` (formatting)
86+
- Trailing whitespace, end-of-file, and YAML checks
87+
88+
### Running Locally
89+
- **Lint:** `golangci-lint run`
90+
- **Format:** `golangci-lint fmt ./...`
91+
- **Test:** `go test ./zulip/...`
92+
- **Pre-commit:** `pre-commit run --all-files`
93+
94+
---
95+
96+
## Configuration & Authentication
97+
98+
### Client Configuration
99+
Create a client using `zulip.RC` config with email, API key, and server URL:
100+
```go
101+
config := &z.RC{
102+
103+
APIKey: "your-api-key",
104+
Site: "https://zulip.example.com",
105+
}
106+
client, err := client.NewClient(config)
107+
```
108+
109+
Optional configuration:
110+
- Client certificates: `ClientCert`, `ClientCertKey`, `CertBundle`
111+
- Skip TLS verification: `Insecure: true`
112+
- Custom HTTP client: `client.WithHTTPClient(httpClient)`
113+
- Custom logger: Configure via `client.WithLogger()` option (uses `slog` by default)
114+
115+
For testing, there are helper functions that automatically fetch test credentials from the development server.
116+
117+
118+
---
119+
120+
## Critical Patterns & Conventions
121+
122+
### 1. **Error Handling**
123+
Three error types to know:
124+
- `APIError` - General API failures (wraps body and unmarshaled model)
125+
- `CodedError` - Server error with `code` field (e.g., "BAD_REQUEST", "UNAUTHORIZED")
126+
- Special errors: `RateLimitedError`, `BadEventQueueIdError`, `NonExistingChannelIdError`
127+
128+
Example:
129+
```go
130+
resp, _, err := client.GetUser(ctx, 123).Execute()
131+
if err != nil {
132+
var codedErr z.CodedError
133+
if errors.As(err, &codedErr) {
134+
log.Printf("API error %s: %s", codedErr.Code, codedErr.Msg)
135+
}
136+
}
137+
```
138+
139+
### 2. **Union Types** (in `zulip/internal/utils/union.go`)
140+
Handles OpenAPI discriminated unions using reflection-based marshaling/unmarshaling:
141+
- Struct with pointer fields for each union variant
142+
- `MarshalUnionType()`/`UnmarshalUnionType()` ensures exactly ONE field is set
143+
- Used for ambiguous response types (e.g., narrow types, event types)
144+
145+
**Note:** Event types live in the `events` package (e.g., `events/message_event.go`) instead of the `zulip` package, because they blow up the package docks significantly and the real-time events API behaves very differently.
146+
147+
### 3. **Narrow Types** (in `narrow.go`)
148+
Represents query filters for messages - complex union type with 15+ variants:
149+
```go
150+
client.GetMessages(ctx).
151+
Narrow(z.Where(z.ChannelNameIs("announcements")).
152+
And(z.TopicIs("releases")))
153+
```
154+
155+
### 4. **Response Objects**
156+
All API responses inherit from `Response`:
157+
```go
158+
type Response struct {
159+
Result string `json:"result"` // "success" or "error"
160+
Msg string `json:"msg,omitempty"` // error message if result="error"
161+
}
162+
```
163+
Specific responses add domain fields (e.g., `GetUsersResponse.Members []User`).
164+
165+
**Event types are now located in the `events` package.** For example, see `events/message_event.go` for message event types.
166+
167+
### 5. **Timestamp Handling**
168+
Timestamps from Zulip API are returned as integers or floats, but the client converts them to from and to `time.Time` or `time.Duration` transparently.
169+
170+
171+
---
172+
173+
## Testing Patterns
174+
175+
### Test File Structure
176+
Tests follow standard Go patterns with integration focus:
177+
- Tests in `*_test.go` files in separate `zulip_test` package
178+
- Requires valid Zulip server running in development mode
179+
- Tests use helper functions defined within test files
180+
181+
### Common Test Patterns
182+
```go
183+
// 1. Setup - create resources
184+
channelID := createChannelWithAllClients(t)
185+
186+
// 2. Execute
187+
resp, _, err := client.AddReaction(ctx, msgID).
188+
EmojiName("smile").
189+
Execute()
190+
191+
// 3. Verify
192+
require.NoError(t, err)
193+
require.NotNil(t, resp)
194+
```
195+
196+
### Multi-Client Testing
197+
Tests often run against multiple clients (normal, admin, bot) to verify permission handling:
198+
```go
199+
t.Run("TestName", RunForAllClients(t, func(t *testing.T, apiClient z.Client) {
200+
// test runs once per client
201+
}))
202+
```
203+
204+
---
205+
206+
207+
### Build, Lint, and Test
208+
```bash
209+
# Build
210+
go build ./...
211+
212+
# Lint (static analysis)
213+
golangci-lint run
214+
215+
# Format
216+
golangci-lint fmt ./...
217+
218+
# Run all tests (requires live Zulip server)
219+
go test ./... -v
220+
221+
# Run specific test
222+
go test ./... -run TestName
223+
224+
# Run all pre-commit hooks
225+
pre-commit run -a
226+
```
227+
228+
### Manual Code Organization
229+
This is a hand-written codebase with clear separation of concerns:
230+
- `zulip/api/*/api_*.go` - API domain service implementations with request builders
231+
- `zulip/api/*/api_*_responses.go` - Response types organized by domain
232+
- `zulip/*.go` - Data models and enums
233+
- `zulip/client/client.go` - Main client aggregating all API services
234+
- `zulip/internal/api_client/api_client.go` - Core HTTP client implementation
235+
- `zulip/errors.go` - Error type definitions
236+
- `zulip/internal/utils/union.go` - Union type marshaling logic
237+
- `zulip/zuliprc/zuliprc.go` - Configuration loading from INI files
238+
- `*_test.go` - Integration tests
239+
240+
---
241+
242+
243+
## Key Files & Directories
244+
245+
| File/Dir | Purpose |
246+
|----------|---------|
247+
| `zulip/client/client.go` | Client interface definition and service aggregation |
248+
| `internal/clients/` | HTTP client implementations and helpers |
249+
| `internal/union/union.go` | Union type marshaling logic |
250+
| `zulip/rc.go` | Configuration loading from INI files |
251+
| `zulip/errors.go` | Error type definitions |
252+
| `zulip/api/*/api_*.go` | Domain-specific API service implementations |
253+
| `zulip/events/` | Event types and event handling |
254+
| `zulip/*.go` | Data model structs and enums |
255+
| `*_test.go` | Integration tests |
256+
257+
258+
---
259+
260+
## When Adding New Features
261+
1. **Add to appropriate API interface** - Domain files already have interface definitions
262+
2. **Update request type** - Add request struct and setter methods (follow existing patterns in `api_*.go`)
263+
3. **Write integration tests** - Use helpers and `RunForAllClients()` for multi-client testing
264+
4. **Handle edge cases** - Account for permission-based API differences (admin vs user vs bot)
265+
5. **Update documentation** - Ensure README and code comments reflect new functionality
266+
6. **Lint and test** - Run `golangci-lint` and tests to ensure code quality

0 commit comments

Comments
 (0)