Skip to content

Commit 4b63726

Browse files
committed
docs: extend CONTRIBUTING.md with code guidelines
Add detailed code style and documentation guidelines including: - Method documentation conventions with godoc examples - API endpoint documentation patterns - Error handling guidelines - Type naming and struct conventions - Testing requirements and patterns Co-authored-by: review feedback from google/go-github maintainers
1 parent 5475166 commit 4b63726

1 file changed

Lines changed: 278 additions & 20 deletions

File tree

CONTRIBUTING.md

Lines changed: 278 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,13 @@ tips (which are frequently ignored by AI-driven PRs):
151151
* When possible, try to make smaller, focused PRs (which are easier to review
152152
and easier for others to understand).
153153

154-
## Code Comments
154+
## Code Guidelines
155+
156+
This section documents common code patterns and conventions used throughout
157+
the go-github repository. Following these guidelines helps maintain consistency
158+
and makes the codebase easier to understand and maintain.
159+
160+
### Code Comments
155161

156162
Every exported method and type needs to have code comments that follow
157163
[Go Doc Comments][]. A typical method's comments will look like this:
@@ -165,18 +171,18 @@ Every exported method and type needs to have code comments that follow
165171
func (s *RepositoriesService) Get(ctx context.Context, owner, repo string) (*Repository, *Response, error) {
166172
u := fmt.Sprintf("repos/%v/%v", owner, repo)
167173
req, err := s.client.NewRequest(ctx, "GET", u, nil)
168-
// ...
174+
...
169175
}
170176
```
171177
And the returned type `Repository` will have comments like this:
172178

173179
```go
174180
// Repository represents a GitHub repository.
175181
type Repository struct {
176-
ID *int64 `json:"id,omitempty"`
182+
ID *int64 `json:"id,omitempty"`
177183
NodeID *string `json:"node_id,omitempty"`
178-
Owner *User `json:"owner,omitempty"`
179-
// ...
184+
Owner *User `json:"owner,omitempty"`
185+
...
180186
}
181187
```
182188

@@ -199,6 +205,273 @@ description.
199205

200206
[Go Doc Comments]: https://go.dev/doc/comment
201207

208+
### File Organization
209+
210+
Files are organized by service and API endpoint, following the pattern
211+
`{service}_{api}.go`. For example:
212+
- `repos_contents.go` - Repository contents API methods
213+
- `users_ssh_signing_keys.go` - User SSH signing keys API methods
214+
- `orgs_rules.go` - Organization rules API methods
215+
216+
Test files follow the pattern `{service}_{api}_test.go`.
217+
218+
These services map directly to how the [GitHub API documentation][] is
219+
organized, so use that as your guide for where to put new methods.
220+
221+
For example, methods defined at <https://docs.github.com/en/rest/webhooks/repos> live in [repos_hooks.go](https://github.com/google/go-github/blob/master/github/repos_hooks.go).
222+
223+
[GitHub API documentation]: https://docs.github.com/en/rest
224+
225+
### Naming Conventions
226+
227+
#### Receiver Names
228+
229+
Service method receivers consistently use the single letter `s`:
230+
231+
```go
232+
func (s *RepositoriesService) Get(ctx context.Context, owner, repo string) (*Repository, *Response, error) {
233+
// ...
234+
}
235+
```
236+
237+
#### Method Names
238+
239+
Methods use descriptive names that clearly indicate their action.
240+
The method name should not repeat the scope already defined by the service:
241+
242+
```go
243+
// On OrganizationsService, the scope is already "organization":
244+
func (s *OrganizationsService) ListMembers(ctx context.Context, org string, opts *OrganizationListMembersOptions) ([]*User, *Response, error)
245+
246+
// On EnterpriseService, the scope is already "enterprise":
247+
func (s *EnterpriseService) GetLicenseInfo(ctx context.Context) (*LicenseInfo, *Response, error)
248+
```
249+
250+
Common method name prefixes:
251+
- `Get` - Retrieve a single resource
252+
- `List` - Retrieve multiple resources (supports pagination)
253+
- `Create` - Create a new resource
254+
- `Update` - Update an existing resource
255+
- `Delete` - Delete a resource
256+
257+
#### Common Variable Names
258+
259+
- `ctx` - Context
260+
- `u` - URL string
261+
- `req` - HTTP request
262+
- `resp` - HTTP response
263+
- `result` - Result from API call
264+
- `err` - Error
265+
- `opts` - Options parameter
266+
- `owner` - Repository owner (username or organization)
267+
- `repo` - Repository name
268+
- `org` - Organization name
269+
- `user` - Username
270+
- `team` - Team name or slug
271+
- `project` - Project name or number
272+
273+
### Type Conventions
274+
275+
#### Value vs Pointer Parameters
276+
277+
Prefer value types over pointer types for parameters where the distinction
278+
between "zero" and "unset" is not needed, or where the type is small and
279+
cheap to copy (e.g., `string`, `int`, `int64`, `bool`). Use pointer types
280+
when you need to distinguish between an unset value and a zero value.
281+
See [#3644][] and [#3887][] for background discussion.
282+
283+
[#3644]: https://github.com/google/go-github/pull/3644
284+
[#3887]: https://github.com/google/go-github/pull/3887
285+
286+
#### Request Option Types
287+
288+
Request option types (for query parameters) are named after the method they
289+
modify, with the suffix `Options`:
290+
291+
```go
292+
type RepositoryListOptions struct {
293+
Type string `url:"type,omitempty"`
294+
Sort string `url:"sort,omitempty"`
295+
Direction string `url:"direction,omitempty"`
296+
ListOptions
297+
}
298+
```
299+
300+
#### Request Body Types
301+
302+
Request body types (for POST/PUT/PATCH requests) should use the `Request`
303+
suffix for create and update operations:
304+
305+
```go
306+
type CreateHostedRunnerRequest struct {
307+
Name string `json:"name"`
308+
RunnerGroupId int64 `json:"runner_group_id"` // Required, non-pointer
309+
EnableStaticIP *bool `json:"enable_static_ip,omitempty"`
310+
Image string `json:"image"`
311+
VMSize string `json:"vm_size"`
312+
}
313+
```
314+
315+
#### Response Types
316+
317+
Response types are named after the resource they represent, typically without
318+
any suffix:
319+
320+
```go
321+
type Repository struct {
322+
ID *int64 `json:"id,omitempty"`
323+
Name *string `json:"name,omitempty"`
324+
FullName *string `json:"full_name,omitempty"`
325+
Description *string `json:"description,omitempty"`
326+
// ...
327+
}
328+
```
329+
330+
#### Common Structs
331+
332+
- `ListOptions` - For offset-based pagination (page/per_page)
333+
- `ListCursorOptions` - For cursor-based pagination
334+
- `UploadOptions` - For file uploads
335+
- `Response` - Wraps the HTTP response
336+
337+
### JSON Tags
338+
339+
#### Request Bodies
340+
341+
Required fields should be non-pointer types without `omitempty`:
342+
343+
```go
344+
type SecretScanningAlertUpdateOptions struct {
345+
State string `json:"state"`
346+
// ...
347+
}
348+
```
349+
350+
Optional fields should be pointer types with `omitempty`:
351+
352+
```go
353+
type SecretScanningAlertUpdateOptions struct {
354+
State string `json:"state"`
355+
Resolution *string `json:"resolution,omitempty"`
356+
ResolutionComment *string `json:"resolution_comment,omitempty"`
357+
}
358+
```
359+
360+
Use `omitzero` for structs and `time.Time` where you want to omit
361+
empty values (not just nil). For slices and maps, `omitzero` has the
362+
opposite behavior: it keeps empty (non-nil) values and only omits nil
363+
values. See `RepositoryRuleset` for examples of `omitzero` usage with
364+
both slices and structs.
365+
366+
For optional boolean fields where you need to distinguish between `false`
367+
and "not set", use `*bool` with `omitzero`.
368+
369+
#### Response Bodies
370+
371+
Follow the same conventions as request bodies for `omitempty` and
372+
`omitzero` usage. Optional fields should use pointer types with
373+
`omitempty`, and required fields should prefer non-pointer types.
374+
See [Common Types](#common-types) for conventions on ID, Node ID,
375+
Timestamp, and Boolean fields.
376+
377+
### URL Tags for Query Parameters
378+
379+
All fields should use `url` tags with `omitempty` to omit zero values
380+
from the query string:
381+
382+
```go
383+
type RepositoryContentGetOptions struct {
384+
Ref string `url:"ref,omitempty"`
385+
}
386+
387+
type RepositoryListOptions struct {
388+
Type string `url:"type,omitempty"`
389+
Sort string `url:"sort,omitempty"`
390+
Direction string `url:"direction,omitempty"`
391+
ListOptions
392+
}
393+
```
394+
395+
### Pagination
396+
397+
The go-github library supports two types of pagination:
398+
399+
#### Offset-based Pagination
400+
401+
Use `ListOptions` for APIs that use page/per_page parameters:
402+
403+
```go
404+
type ListOptions struct {
405+
Page int `url:"page,omitempty"`
406+
PerPage int `url:"per_page,omitempty"`
407+
}
408+
```
409+
410+
#### Cursor-based Pagination
411+
412+
Use `ListCursorOptions` for APIs that use cursor-based pagination:
413+
414+
```go
415+
type ListCursorOptions struct {
416+
Page string `url:"page,omitempty"`
417+
PerPage int `url:"per_page,omitempty"`
418+
First int `url:"first,omitempty"`
419+
Last int `url:"last,omitempty"`
420+
After string `url:"after,omitempty"`
421+
Before string `url:"before,omitempty"`
422+
Cursor string `url:"cursor,omitempty"`
423+
}
424+
```
425+
426+
Embed the appropriate pagination options in your option structs
427+
based on the API's pagination model: use `ListOptions` for
428+
offset-based APIs and `ListCursorOptions` for cursor-based APIs.
429+
The library automatically generates iterator methods (e.g., `ListIter`)
430+
for methods that start with `List` and return a slice.
431+
432+
For APIs with non-standard pagination behavior (e.g., methods that
433+
return a wrapper struct containing multiple slices), configuration maps
434+
in `gen-iterators.go` can be adjusted — including `useCursorPagination`,
435+
`customNames`, `sliceToBeUsedForIteration`, and `customTestJSON`.
436+
437+
### Common Types
438+
439+
#### ID Fields
440+
441+
GitHub API IDs are always `int64`. In request bodies, use non-pointer `int64`
442+
for required fields and `*int64` for optional fields. In response bodies, use
443+
`*int64` with `omitempty`:
444+
445+
```go
446+
type CreateHostedRunnerRequest struct {
447+
RunnerGroupId int64 `json:"runner_group_id"` // Required, non-pointer
448+
}
449+
```
450+
451+
#### Node ID Fields
452+
453+
Node IDs are always `string`:
454+
455+
```go
456+
type Repository struct {
457+
NodeID *string `json:"node_id,omitempty"`
458+
// ...
459+
}
460+
```
461+
462+
#### Timestamp Fields
463+
464+
Use the `Timestamp` type for all date/time fields:
465+
466+
```go
467+
type Repository struct {
468+
CreatedAt *Timestamp `json:"created_at,omitempty"`
469+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
470+
PushedAt *Timestamp `json:"pushed_at,omitempty"`
471+
// ...
472+
}
473+
```
474+
202475
## Metadata
203476

204477
GitHub publishes [OpenAPI descriptions of their API][]. We use these
@@ -290,21 +563,6 @@ section for more information.
290563

291564
**script/test.sh** runs tests on all modules.
292565

293-
## Other notes on code organization
294-
295-
Currently, everything is defined in the main `github` package, with API methods
296-
broken into separate service objects. These services map directly to how
297-
the [GitHub API documentation][] is organized, so use that as your guide for
298-
where to put new methods.
299-
300-
Code is organized in files also based pretty closely on the GitHub API
301-
documentation, following the format `{service}_{api}.go`. For example, methods
302-
defined at <https://docs.github.com/en/rest/webhooks/repos> live in
303-
[repos_hooks.go][].
304-
305-
[GitHub API documentation]: https://docs.github.com/en/rest
306-
[repos_hooks.go]: https://github.com/google/go-github/blob/master/github/repos_hooks.go
307-
308566
## Maintainer's Guide
309567

310568
(These notes are mostly only for people merging in pull requests.)

0 commit comments

Comments
 (0)