Skip to content

Commit 6fcb261

Browse files
committed
docs: extend CONTRIBUTING.md with code guidelines
Add comprehensive section documenting common code patterns and conventions: - Naming conventions (files, receivers, types, methods, variables) - JSON tags for request bodies (required vs optional fields, omitzero) - URL tags for query parameters (non-pointer fields with omitempty) - Pagination patterns (ListOptions, ListCursorOptions, best practices) - Common types (ID, NodeID, Timestamp, boolean fields) - Generation patterns (accessors, iterators, documentation, linter rules) This addresses issue #4023 and helps new contributors understand the codebase better, reducing review burden and improving code consistency.
1 parent 6c58592 commit 6fcb261

1 file changed

Lines changed: 349 additions & 0 deletions

File tree

CONTRIBUTING.md

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,355 @@ description.
199199

200200
[Go Doc Comments]: https://go.dev/doc/comment
201201

202+
## Code Guidelines
203+
204+
This section documents common code patterns and conventions used throughout
205+
the go-github repository. Following these guidelines helps maintain consistency
206+
and makes the codebase easier to understand and maintain.
207+
208+
### Naming Conventions
209+
210+
#### File Names
211+
212+
Files are organized by service and API endpoint, following the pattern
213+
`{service}_{api}.go`. For example:
214+
- `repos_contents.go` - Repository contents API methods
215+
- `users_ssh_signing_keys.go` - User SSH signing keys API methods
216+
- `orgs_rules.go` - Organization rules API methods
217+
218+
Test files follow the pattern `{service}_{api}_test.go`.
219+
220+
#### Receiver Names
221+
222+
Service method receivers consistently use the single letter `s`:
223+
224+
```go
225+
func (s *RepositoriesService) Get(ctx context.Context, owner, repo string) (*Repository, *Response, error) {
226+
// ...
227+
}
228+
```
229+
230+
#### Request Option Types
231+
232+
Request option types (for query parameters) are named after the method they
233+
modify, with the suffix `Options`:
234+
235+
```go
236+
type RepositoryListOptions struct {
237+
Type string `url:"type,omitempty"`
238+
Sort string `url:"sort,omitempty"`
239+
Direction string `url:"direction,omitempty"`
240+
ListOptions
241+
}
242+
```
243+
244+
#### Request Body Types
245+
246+
Request body types (for POST/PUT/PATCH requests) are named after the method
247+
they modify, with the suffix `Options`:
248+
249+
```go
250+
type RepositoryContentFileOptions struct {
251+
Message *string `json:"message,omitempty"`
252+
Content []byte `json:"content"`
253+
SHA *string `json:"sha,omitempty"`
254+
Branch *string `json:"branch,omitempty"`
255+
Author *CommitAuthor `json:"author,omitempty"`
256+
Committer *CommitAuthor `json:"committer,omitempty"`
257+
}
258+
```
259+
260+
#### Response Types
261+
262+
Response types are named after the resource they represent, typically without
263+
any suffix:
264+
265+
```go
266+
type Repository struct {
267+
ID *int64 `json:"id,omitempty"`
268+
Name *string `json:"name,omitempty"`
269+
FullName *string `json:"full_name,omitempty"`
270+
Description *string `json:"description,omitempty"`
271+
// ...
272+
}
273+
```
274+
275+
#### Method and Variable Naming
276+
277+
Methods use descriptive names that clearly indicate their action:
278+
- `Get` - Retrieve a single resource
279+
- `List` - Retrieve multiple resources (supports pagination)
280+
- `Create` - Create a new resource
281+
- `Update` - Update an existing resource
282+
- `Delete` - Delete a resource
283+
- `Edit` - Edit an existing resource (alternative to Update)
284+
285+
Common local variable names:
286+
- `ctx` - Context
287+
- `u` - URL string
288+
- `req` - HTTP request
289+
- `resp` - HTTP response
290+
- `result` - Result from API call
291+
- `err` - Error
292+
293+
#### Common Variable Names
294+
295+
These variable names are used consistently throughout the codebase:
296+
- `owner` - Repository owner (username or organization)
297+
- `repo` - Repository name
298+
- `org` - Organization name
299+
- `user` - Username
300+
- `team` - Team name or slug
301+
- `project` - Project name or number
302+
303+
#### Enterprise and Organization Scoped Methods
304+
305+
Methods that operate on enterprise or organization resources include the scope
306+
in their name:
307+
308+
```go
309+
// Enterprise-scoped methods
310+
func (s *EnterpriseService) GetLicenseInfo(ctx context.Context) (*LicenseInfo, *Response, error)
311+
312+
// Organization-scoped methods
313+
func (s *OrganizationsService) ListMembers(ctx context.Context, org string, opts *OrganizationListMembersOptions) ([]*User, *Response, error)
314+
```
315+
316+
#### Common Structs
317+
318+
These common structs are used throughout the codebase:
319+
- `ListOptions` - For offset-based pagination (page/per_page)
320+
- `ListCursorOptions` - For cursor-based pagination
321+
- `UploadOptions` - For file uploads
322+
- `Response` - Wraps the HTTP response
323+
324+
### JSON Tags for Request Bodies
325+
326+
When defining structs that represent a request body (sent via POST/PUT/PATCH):
327+
328+
1. **Required fields** should be non-pointer types without `omitempty`:
329+
330+
```go
331+
type SecretScanningAlertUpdateOptions struct {
332+
State string `json:"state"` // Required field
333+
// ...
334+
}
335+
```
336+
337+
2. **Optional fields** should be pointer types with `omitempty`:
338+
339+
```go
340+
type SecretScanningAlertUpdateOptions struct {
341+
State string `json:"state"`
342+
Resolution *string `json:"resolution,omitempty"`
343+
ResolutionComment *string `json:"resolution_comment,omitempty"`
344+
}
345+
```
346+
347+
3. **Use `omitzero` for structs and slices** where you want to omit empty values
348+
(not just nil):
349+
350+
```go
351+
type ActivityNotificationOptions struct {
352+
SubjectType string `json:"subject_type,omitempty"`
353+
Subject *string `json:"subject,omitempty"`
354+
LastReadAt Timestamp `json:"last_read_at,omitzero"`
355+
}
356+
```
357+
358+
### URL Tags for Query Parameters
359+
360+
When defining structs that represent query parameters (sent via URL):
361+
362+
1. **All fields should be non-pointer types** with `url` tags:
363+
364+
```go
365+
type RepositoryContentGetOptions struct {
366+
Ref string `url:"ref,omitempty"`
367+
}
368+
369+
type ListOptions struct {
370+
Page int `url:"page,omitempty"`
371+
PerPage int `url:"per_page,omitempty"`
372+
}
373+
```
374+
375+
2. **Use `omitempty` to omit zero values** from the query string:
376+
377+
```go
378+
type RepositoryListOptions struct {
379+
Type string `url:"type,omitempty"`
380+
Sort string `url:"sort,omitempty"`
381+
Direction string `url:"direction,omitempty"`
382+
ListOptions
383+
}
384+
```
385+
386+
### Pagination
387+
388+
The go-github library supports two types of pagination:
389+
390+
#### Offset-based Pagination
391+
392+
Use `ListOptions` for APIs that use page/per_page parameters:
393+
394+
```go
395+
type ListOptions struct {
396+
// For paginated result sets, page of results to retrieve.
397+
Page int `url:"page,omitempty"`
398+
399+
// For paginated result sets, the number of results to include per page.
400+
PerPage int `url:"per_page,omitempty"`
401+
}
402+
```
403+
404+
#### Cursor-based Pagination
405+
406+
Use `ListCursorOptions` for APIs that use cursor-based pagination:
407+
408+
```go
409+
type ListCursorOptions struct {
410+
// For paginated result sets, page of results to retrieve.
411+
Page string `url:"page,omitempty"`
412+
413+
// For paginated result sets, the number of results to include per page.
414+
PerPage int `url:"per_page,omitempty"`
415+
416+
// For paginated result sets, the number of results per page (max 100), starting from the first matching result.
417+
// This parameter must not be used in combination with last.
418+
First int `url:"first,omitempty"`
419+
420+
// For paginated result sets, the number of results per page (max 100), starting from the last matching result.
421+
// This parameter must not be used in combination with first.
422+
Last int `url:"last,omitempty"`
423+
424+
// A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
425+
After string `url:"after,omitempty"`
426+
427+
// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
428+
Before string `url:"before,omitempty"`
429+
430+
// A cursor, as given in the Link header. If specified, the query continues the search using this cursor.
431+
Cursor string `url:"cursor,omitempty"`
432+
}
433+
```
434+
435+
#### Pagination Best Practices
436+
437+
1. **Embed pagination options** in your option structs:
438+
439+
```go
440+
type RepositoryListOptions struct {
441+
Type string `url:"type,omitempty"`
442+
Sort string `url:"sort,omitempty"`
443+
Direction string `url:"direction,omitempty"`
444+
ListOptions // Embed for pagination
445+
}
446+
```
447+
448+
2. **Handle nil options** in your methods:
449+
450+
```go
451+
func (s *RepositoriesService) List(ctx context.Context, user string, opts *RepositoryListOptions) ([]*Repository, *Response, error) {
452+
if opts == nil {
453+
opts = &RepositoryListOptions{}
454+
}
455+
// ...
456+
}
457+
```
458+
459+
3. **Use iterators** for list methods that support pagination. The library
460+
automatically generates iterator methods (e.g., `ListIter`) for methods
461+
that start with `List` and return a slice.
462+
463+
### Common Types
464+
465+
These types are used consistently throughout the codebase:
466+
467+
#### ID Fields
468+
469+
GitHub API IDs are always `int64`:
470+
471+
```go
472+
type Repository struct {
473+
ID *int64 `json:"id,omitempty"`
474+
// ...
475+
}
476+
477+
type User struct {
478+
ID *int64 `json:"id,omitempty"`
479+
// ...
480+
}
481+
```
482+
483+
#### Node ID Fields
484+
485+
Node IDs are always `string`:
486+
487+
```go
488+
type Repository struct {
489+
ID *int64 `json:"id,omitempty"`
490+
NodeID *string `json:"node_id,omitempty"`
491+
// ...
492+
}
493+
```
494+
495+
#### Timestamp Fields
496+
497+
Use the `Timestamp` type for all date/time fields:
498+
499+
```go
500+
type Repository struct {
501+
CreatedAt *Timestamp `json:"created_at,omitempty"`
502+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
503+
PushedAt *Timestamp `json:"pushed_at,omitempty"`
504+
// ...
505+
}
506+
```
507+
508+
#### Boolean Fields
509+
510+
Boolean fields are always `*bool` (pointer to bool) to distinguish between
511+
`false` and `not set`:
512+
513+
```go
514+
type Repository struct {
515+
Private *bool `json:"private,omitempty"`
516+
Fork *bool `json:"fork,omitempty"`
517+
// ...
518+
}
519+
```
520+
521+
### Generation Patterns
522+
523+
The go-github repository uses code generation for several purposes:
524+
525+
#### Generated Accessors
526+
527+
The `github-accessors.go` file contains generated getter and setter methods
528+
for struct fields. These are generated automatically and should not be edited
529+
manually.
530+
531+
#### Generated Iterators
532+
533+
Iterator methods are automatically generated for list methods that:
534+
1. Start with `List`
535+
2. Return a slice
536+
3. Accept pagination options
537+
538+
For example, `RepositoriesService.List` automatically gets a `ListIter` method.
539+
540+
#### Generated Documentation
541+
542+
Documentation links are automatically generated from `openapi_operations.yaml`.
543+
Run `script/generate.sh` to update these links.
544+
545+
#### Linter Rules
546+
547+
The repository uses custom linter rules to enforce consistency:
548+
- `sliceofpointers` - Ensures slice fields use pointers
549+
- `structfield` - Ensures struct fields follow naming conventions
550+
202551
## Metadata
203552

204553
GitHub publishes [OpenAPI descriptions of their API][]. We use these

0 commit comments

Comments
 (0)