@@ -180,7 +180,6 @@ And the returned type `Repository` will have comments like this:
180180// Repository represents a GitHub repository.
181181type Repository struct {
182182 ID *int64 ` json:"id,omitempty"`
183- NodeID *string ` json:"node_id,omitempty"`
184183 Owner *User ` json:"owner,omitempty"`
185184 // ...
186185}
@@ -222,6 +221,28 @@ For example, methods defined at <https://docs.github.com/en/rest/webhooks/repos>
222221
223222[ GitHub API documentation ] : https://docs.github.com/en/rest
224223
224+ ### Services
225+
226+ The API is split into services, one per logical area of the GitHub API
227+ (e.g. ` RepositoriesService ` , ` UsersService ` ). Each service is a named type
228+ over the shared ` service ` struct, which holds a back-reference to the ` Client ` :
229+
230+ ``` go
231+ type service struct {
232+ client *Client
233+ }
234+
235+ type RepositoriesService service
236+ ```
237+
238+ All services share a single ` service ` value embedded in the ` Client ` as
239+ ` common ` , so adding a service does not allocate. To add a new service:
240+
241+ 1 . Add a field for it on the ` Client ` struct, keeping the list alphabetical
242+ 2 . Wire it up in ` newClient ` (alongside the other ` c.X = (*XService)(&c.common) ` lines, also kept alphabetical).
243+ 3 . Declare the service type in the service's file (e.g. ` repos.go ` ) and hang the
244+ methods off it, using ` s ` as the receiver (see [ Receiver Names] ( #receiver-names ) ).
245+
225246### Naming Conventions
226247
227248#### Receiver Names
@@ -263,6 +284,7 @@ Common method name prefixes:
263284- `result` - Result from API call
264285- `err` - Error
265286- `opts` - Options parameter
287+ - `body` - Body parameter
266288- `owner` - Repository owner (username or organization)
267289- `repo` - Repository name
268290- `org` - Organization name
@@ -283,30 +305,45 @@ See [#3644][] and [#3887][] for background discussion.
283305[#3644]: https:// github.com/google/go-github/pull/3644
284306[#3887]: https:// github.com/google/go-github/pull/3887
285307
308+ #### Creating Pointers
309+
310+ Pointer fields are common because many request and response fields are
311+ optional. Use the generic `Ptr` helper to take the address of a literal
312+ instead of declaring an intermediate variable:
313+
314+ ```go
315+ repo := &Repository{
316+ Name: Ptr (" go-github" ),
317+ Private: Ptr (true ),
318+ ID: Ptr (int64 (1 )),
319+ }
320+ ```
321+
286322#### Request Option Types
287323
288- Request option types ( for query parameters) are named after the method they
324+ Request option types should be used for query parameters and named after the method they
289325modify, with the suffix ` Options ` :
290326
291327``` go
292328type RepositoryListOptions struct {
293- Type string ` url:"type,omitempty"`
294- Sort string ` url:"sort,omitempty"`
329+ Type string ` url:"type,omitempty"`
330+ Sort string ` url:"sort,omitempty"`
295331 Direction string ` url:"direction,omitempty"`
296332 ListOptions
297333}
298334```
299335
300336#### Request Body Types
301337
302- Request body types ( for POST/PUT/PATCH requests) should use the ` Request `
338+ Request body types for POST/PUT/PATCH requests should use the ` Request `
303339suffix for create and update operations:
304340
305341``` go
306- type CreateSelfHostedRunnerGroupRequest struct {
307- Name string ` json:"name"`
308- RunnerGroupId int64 ` json:"runner_group_id"`
309- Visibility string ` json:"visibility,omitempty"`
342+ type CreateHostedRunnerRequest struct {
343+ Name string ` json:"name"`
344+ RunnerGroupID int64 ` json:"runner_group_id"`
345+ MaximumRunners *int64 ` json:"maximum_runners,omitempty"`
346+ // ...
310347}
311348```
312349
@@ -317,9 +354,9 @@ any suffix:
317354
318355``` go
319356type Repository struct {
320- ID *int64 ` json:"id,omitempty"`
321- Name *string ` json:"name,omitempty"`
322- FullName *string ` json:"full_name,omitempty"`
357+ ID *int64 ` json:"id,omitempty"`
358+ Name *string ` json:"name,omitempty"`
359+ FullName *string ` json:"full_name,omitempty"`
323360 Description *string ` json:"description,omitempty"`
324361 // ...
325362}
@@ -336,31 +373,34 @@ type Repository struct {
336373
337374#### Request Bodies
338375
339- Required fields should be non-pointer types without ` omitempty ` :
376+ Required fields should be non-pointer types without ` omitempty ` .
377+ Optional fields should be pointer types with ` omitempty ` .
378+ Use ` omitzero ` for structs and ` time.Time ` where you want to omit
379+ empty values (not just nil). For slices and maps, ` omitzero ` has the
380+ opposite behavior: it keeps empty (non-nil) values and only omits nil
381+ values.
340382
341383``` go
342- type SecretScanningAlertUpdateOptions struct {
343- State string ` json:"state"`
344- // ...
345- }
346- ```
384+ type RepositoryRuleset struct {
385+ // ID is optional.
386+ ID *int64 ` json:"id,omitempty"`
347387
348- Optional fields should be pointer types with ` omitempty ` :
388+ // Name is required.
389+ Name string ` json:"name"`
349390
350- ``` go
351- type SecretScanningAlertUpdateOptions struct {
352- State string ` json:"state"`
353- Resolution *string ` json:"resolution,omitempty"`
354- ResolutionComment *string ` json:"resolution_comment,omitempty"`
391+ // Target is optional struct.
392+ Target *RulesetTarget ` json:"target,omitempty"`
393+
394+ // BypassActors is optional.
395+ BypassActors []*BypassActor ` json:"bypass_actors,omitzero"`
396+
397+ // CreatedAt is optional.
398+ CreatedAt *Timestamp ` json:"created_at,omitempty"`
399+
400+ // ...
355401}
356402```
357403
358- Use ` omitzero ` for structs and ` time.Time ` where you want to omit
359- empty values (not just nil). For slices and maps, ` omitzero ` has the
360- opposite behavior: it keeps empty (non-nil) values and only omits nil
361- values. See ` RepositoryRuleset ` for examples of ` omitzero ` usage with
362- both slices and structs.
363-
364404For optional boolean fields where you need to distinguish between ` false `
365405and "not set", use ` *bool ` with ` omitzero ` .
366406
@@ -369,22 +409,17 @@ and "not set", use `*bool` with `omitzero`.
369409Follow the same conventions as request bodies for ` omitempty ` and
370410` omitzero ` usage. Optional fields should use pointer types with
371411` omitempty ` , and required fields should prefer non-pointer types.
372- See [ Common Types] ( #common-types ) for conventions on ID, Node ID,
373- Timestamp, and Boolean fields.
412+ See [ Common Types] ( #common-types ) for conventions on ID, Node ID, and Timestamp.
374413
375414### URL Tags for Query Parameters
376415
377416All fields should use ` url ` tags with ` omitempty ` to omit zero values
378417from the query string:
379418
380419``` go
381- type RepositoryContentGetOptions struct {
382- Ref string ` url:"ref,omitempty"`
383- }
384-
385420type RepositoryListOptions struct {
386- Type string ` url:"type,omitempty"`
387- Sort string ` url:"sort,omitempty"`
421+ Type string ` url:"type,omitempty"`
422+ Sort string ` url:"sort,omitempty"`
388423 Direction string ` url:"direction,omitempty"`
389424 ListOptions
390425}
@@ -400,7 +435,7 @@ Use `ListOptions` for APIs that use page/per_page parameters:
400435
401436``` go
402437type ListOptions struct {
403- Page int ` url:"page,omitempty"`
438+ Page int ` url:"page,omitempty"`
404439 PerPage int ` url:"per_page,omitempty"`
405440}
406441```
@@ -411,13 +446,13 @@ Use `ListCursorOptions` for APIs that use cursor-based pagination:
411446
412447``` go
413448type ListCursorOptions struct {
414- Page string ` url:"page,omitempty"`
415- PerPage int ` url:"per_page,omitempty"`
416- First int ` url:"first,omitempty"`
417- Last int ` url:"last,omitempty"`
418- After string ` url:"after,omitempty"`
419- Before string ` url:"before,omitempty"`
420- Cursor string ` url:"cursor,omitempty"`
449+ Page string ` url:"page,omitempty"`
450+ PerPage int ` url:"per_page,omitempty"`
451+ First int ` url:"first,omitempty"`
452+ Last int ` url:"last,omitempty"`
453+ After string ` url:"after,omitempty"`
454+ Before string ` url:"before,omitempty"`
455+ Cursor string ` url:"cursor,omitempty"`
421456}
422457```
423458
@@ -436,32 +471,23 @@ in `gen-iterators.go` can be adjusted — including `useCursorPagination`,
436471
437472#### ID Fields
438473
439- GitHub API IDs are always ` int64 ` . In request bodies, use non-pointer ` int64 `
440- if the ID is required and ` *int64 ` if the ID is optional. In response bodies,
441- use non-pointer ` int64 ` if the ID is required and ` *int64 ` with ` omitempty `
442- if the ID is optional:
474+ GitHub API IDs are usually ` int64 ` . Use non-pointer ` int64 `
475+ if the ID is required and ` *int64 ` if the ID is optional.
443476
444477``` go
445- // Request body — required ID
446478type CreateHostedRunnerRequest struct {
447479 RunnerGroupID int64 ` json:"runner_group_id"`
448- }
449-
450- // Response body — optional ID
451- type Repository struct {
452- ID *int64 ` json:"id,omitempty"`
453480 // ...
454481}
455482```
456483
457484#### Node ID Fields
458485
459- Node IDs are always ` string ` . In response bodies, ` NodeID ` is typically
460- required and should use a non-pointer type:
486+ Node IDs are usually ` string ` :
461487
462488``` go
463- type Repository struct {
464- NodeID * string ` json:"node_id,omitempty "`
489+ type IssueFieldValue struct {
490+ NodeID string ` json:"node_id"`
465491 // ...
466492}
467493```
@@ -473,12 +499,76 @@ Use the `Timestamp` type for all date/time fields:
473499``` go
474500type Repository struct {
475501 CreatedAt *Timestamp ` json:"created_at,omitempty"`
476- UpdatedAt *Timestamp ` json:"updated_at,omitempty"`
477- PushedAt *Timestamp ` json:"pushed_at,omitempty"`
478502 // ...
479503}
480504```
481505
506+ ### Generated Code
507+
508+ Some files are generated and must never be edited by hand.
509+ When you add or change a struct, run ` script/generate.sh ` to regenerate them.
510+
511+ So after adding a field you typically only write the struct field itself;
512+ the accessor and stringify code follow from ` script/generate.sh ` .
513+ ` script/lint.sh ` will fail if these files are out of date.
514+ Documentation links and ` //meta:operation ` directives are updated
515+ by the same script (see [ Code Comments] ( #code-comments ) ).
516+
517+ ### Testing
518+
519+ Tests use a real ` httptest ` server rather than mocks. Call ` setup ` to get a
520+ ` Client ` pointed at a test server plus the ` mux ` to register handlers on, then
521+ assert on the request and the decoded response. A typical test looks like this:
522+
523+ ``` go
524+ func TestRepositoriesService_GetByID (t *testing .T ) {
525+ t.Parallel ()
526+ client , mux , _ := setup (t)
527+
528+ mux.HandleFunc (" /repositories/1" , func (w http.ResponseWriter , r *http.Request ) {
529+ testMethod (t, r, " GET" )
530+ fmt.Fprint (w, ` {"id":1,"name":"n"}` )
531+ })
532+
533+ ctx := t.Context ()
534+ got , _ , err := client.Repositories .GetByID (ctx, 1 )
535+ if err != nil {
536+ t.Fatalf (" Repositories.GetByID returned error: %v " , err)
537+ }
538+
539+ want := &Repository{ID: Ptr (int64 (1 )), Name: Ptr (" n" )}
540+ if !cmp.Equal (got, want) {
541+ t.Errorf (" Repositories.GetByID returned %+v , want %+v " , got, want)
542+ }
543+
544+ const methodName = " GetByID"
545+ testNewRequestAndDoFailure (t, methodName, client, func () (*Response, error ) {
546+ got , resp , err := client.Repositories .GetByID (ctx, 1 )
547+ if got != nil {
548+ t.Errorf (" testNewRequestAndDoFailure %v = %#v , want nil" , methodName, got)
549+ }
550+ return resp, err
551+ })
552+ }
553+ ```
554+
555+ Conventions to follow:
556+
557+ - Call ` t.Parallel() ` and use ` t.Context() ` for the request context.
558+ - Register handlers on ` mux ` with relative paths (a leading ` / ` ); the test
559+ server fails requests built from absolute URLs to catch that mistake.
560+ - Use the shared helpers instead of reimplementing assertions:
561+ - ` testMethod ` - asserts the HTTP method.
562+ - ` testFormValues ` - asserts query parameters.
563+ - ` testHeader ` - asserts a request header.
564+ - ` testJSONBody ` / ` testPlainBody ` - assert the request body.
565+ - ` testNewRequestAndDoFailure ` - exercises the request-building and
566+ request-doing error paths for a method.
567+ - ` testBadOptions ` - asserts that invalid options return an error.
568+ - Use ` testJSONMarshal ` to verify a type round-trips to and from the expected JSON.
569+ - Compare values with ` cmp.Equal ` and construct optional fields with ` Ptr `
570+ (see [ Creating Pointers] ( #creating-pointers ) ).
571+
482572## Metadata
483573
484574GitHub publishes [ OpenAPI descriptions of their API] [ ] . We use these
0 commit comments