Repository: google/go-github
Pull request #2776 (2023-04-30 12:38:35)

Support array type in go generate with whitelist
Fixes: https://github.com/google/go-github/issues/2425

This PR is similar to the following PR:
- https://github.com/google/go-github/pull/1867

codecov[bot]: ## [Codecov](https://codecov.io/gh/google/go-github/pull/2776?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2776](https://codecov.io/gh/google/go-github/pull/2776?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (6fdb39d) into [master](https://codecov.io/gh/google/go-github/commit/a938017469a86219f8772b0d2341ccde9e2650d4?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (a938017) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##           master    #2776   +/-   ##
=======================================
  Coverage   98.05%   98.05%           
=======================================
  Files         132      132           
  Lines       11638    11638           
=======================================
  Hits        11412    11412           
  Misses        154      154           
  Partials       72       72           
```




olibaa: @gmlewis
Sounds good!
I will fix the code.

Patch:
diff --git a/example/go.mod b/example/go.mod
index 144f51b5..d5a970e6 100644
--- a/example/go.mod
+++ b/example/go.mod
@@ -4,21 +4,21 @@ go 1.17
 
 require (
 	github.com/bradleyfalzon/ghinstallation/v2 v2.0.4
-	github.com/gofri/go-github-ratelimit v1.0.1
+	github.com/gofri/go-github-ratelimit v1.0.3
 	github.com/google/go-github/v52 v52.0.0
-	golang.org/x/crypto v0.1.0
-	golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
+	golang.org/x/crypto v0.7.0
+	golang.org/x/oauth2 v0.7.0
 	google.golang.org/appengine v1.6.7
 )
 
 require (
 	github.com/golang-jwt/jwt/v4 v4.0.0 // indirect
-	github.com/golang/protobuf v1.3.2 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-github/v41 v41.0.0 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
-	golang.org/x/net v0.7.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/term v0.5.0 // indirect
+	golang.org/x/net v0.9.0 // indirect
+	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/term v0.7.0 // indirect
 )
 
 // Use version at HEAD, not the latest published.
diff --git a/example/go.sum b/example/go.sum
index 71e7a992..f231e169 100644
--- a/example/go.sum
+++ b/example/go.sum
@@ -2,12 +2,17 @@ github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 h1:tXKVfhE7FcSkhkv0UwkLvPDeZ4k
 github.com/bradleyfalzon/ghinstallation/v2 v2.0.4/go.mod h1:B40qPqJxWE0jDZgOR1JmaMy+4AY1eBP+IByOvqyAKp0=
 github.com/gofri/go-github-ratelimit v1.0.1 h1:sgefSzxhnvwZ+wR9uZ4l9TnjgLuNiwipJVzJL4YLj9A=
 github.com/gofri/go-github-ratelimit v1.0.1/go.mod h1:OnCi5gV+hAG/LMR7llGhU7yHt44se9sYgKPnafoL7RY=
+github.com/gofri/go-github-ratelimit v1.0.3 h1:Ocs2jaYokZDzgvqaajX+g04dqFyVqL0JQzoO7d2wmlk=
+github.com/gofri/go-github-ratelimit v1.0.3/go.mod h1:OnCi5gV+hAG/LMR7llGhU7yHt44se9sYgKPnafoL7RY=
 github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -21,6 +26,7 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -29,8 +35,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -41,11 +49,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -59,3 +69,5 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
diff --git a/github/event.go b/github/event.go
index 1aabf13b..20907a99 100644
--- a/github/event.go
+++ b/github/event.go
@@ -49,6 +49,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) {
 		payload = &DeployKeyEvent{}
 	case "DeploymentEvent":
 		payload = &DeploymentEvent{}
+	case "DeploymentProtectionRuleEvent":
+		payload = &DeploymentProtectionRuleEvent{}
 	case "DeploymentStatusEvent":
 		payload = &DeploymentStatusEvent{}
 	case "DiscussionEvent":
diff --git a/github/event_types.go b/github/event_types.go
index 685e46f0..6a13b286 100644
--- a/github/event_types.go
+++ b/github/event_types.go
@@ -173,6 +173,25 @@ type DeploymentEvent struct {
 	Installation *Installation `json:"installation,omitempty"`
 }
 
+// DeploymentProtectionRuleEvent represents a deployment protection rule event.
+// The Webhook event name is "deployment_protection_rule".
+//
+// GitHub API docs: https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads#deployment_protection_rule
+type DeploymentProtectionRuleEvent struct {
+	Action      *string `json:"action,omitempty"`
+	Environment *string `json:"environment,omitempty"`
+	Event       *string `json:"event,omitempty"`
+
+	// The URL Github provides for a third-party to use in order to pass/fail a deployment gate
+	DeploymentCallbackURL *string        `json:"deployment_callback_url,omitempty"`
+	Deployment            *Deployment    `json:"deployment,omitempty"`
+	Repo                  *Repository    `json:"repository,omitempty"`
+	Organization          *Organization  `json:"organization,omitempty"`
+	PullRequests          []*PullRequest `json:"pull_requests,omitempty"`
+	Sender                *User          `json:"sender,omitempty"`
+	Installation          *Installation  `json:"installation,omitempty"`
+}
+
 // DeploymentStatusEvent represents a deployment status.
 // The Webhook event name is "deployment_status".
 //
diff --git a/github/gen-accessors.go b/github/gen-accessors.go
index b222465f..4bc22acd 100644
--- a/github/gen-accessors.go
+++ b/github/gen-accessors.go
@@ -51,6 +51,11 @@ var (
 	skipStructs = map[string]bool{
 		"Client": true,
 	}
+
+	// whitelistSliceGetters lists "struct.field" to add getter method
+	whitelistSliceGetters = map[string]bool{
+		"PushEvent.Commits": true,
+	}
 )
 
 func logf(fmt string, args ...interface{}) {
@@ -137,6 +142,12 @@ func (t *templateData) processAST(f *ast.File) error {
 					case *ast.MapType:
 						t.addMapType(x, ts.Name.String(), fieldName.String(), false)
 						continue
+					case *ast.ArrayType:
+						if key := fmt.Sprintf("%v.%v", ts.Name, fieldName); whitelistSliceGetters[key] {
+							logf("Method %v is whitelist; adding getter method.", key)
+							t.addArrayType(x, ts.Name.String(), fieldName.String(), false)
+							continue
+						}
 					}
 
 					logf("Skipping field type %T, fieldName=%v", field.Type, fieldName)
@@ -145,7 +156,7 @@ func (t *templateData) processAST(f *ast.File) error {
 
 				switch x := se.X.(type) {
 				case *ast.ArrayType:
-					t.addArrayType(x, ts.Name.String(), fieldName.String())
+					t.addArrayType(x, ts.Name.String(), fieldName.String(), true)
 				case *ast.Ident:
 					t.addIdent(x, ts.Name.String(), fieldName.String())
 				case *ast.MapType:
@@ -218,17 +229,26 @@ func newGetter(receiverType, fieldName, fieldType, zeroValue string, namedStruct
 	}
 }
 
-func (t *templateData) addArrayType(x *ast.ArrayType, receiverType, fieldName string) {
+func (t *templateData) addArrayType(x *ast.ArrayType, receiverType, fieldName string, isAPointer bool) {
 	var eltType string
+	var ng *getter
 	switch elt := x.Elt.(type) {
 	case *ast.Ident:
 		eltType = elt.String()
+		ng = newGetter(receiverType, fieldName, "[]"+eltType, "nil", false)
+	case *ast.StarExpr:
+		ident, ok := elt.X.(*ast.Ident)
+		if !ok {
+			return
+		}
+		ng = newGetter(receiverType, fieldName, "[]*"+ident.String(), "nil", false)
 	default:
 		logf("addArrayType: type %q, field %q: unknown elt type: %T %+v; skipping.", receiverType, fieldName, elt, elt)
 		return
 	}
 
-	t.Getters = append(t.Getters, newGetter(receiverType, fieldName, "[]"+eltType, "nil", false))
+	ng.ArrayType = !isAPointer
+	t.Getters = append(t.Getters, ng)
 }
 
 func (t *templateData) addIdent(x *ast.Ident, receiverType, fieldName string) {
@@ -322,6 +342,7 @@ type getter struct {
 	ZeroValue    string
 	NamedStruct  bool // Getter for named struct.
 	MapType      bool
+	ArrayType    bool
 }
 
 type byName []*getter
@@ -356,8 +377,8 @@ func ({{.ReceiverVar}} *{{.ReceiverType}}) Get{{.FieldName}}() *{{.FieldType}} {
   }
   return {{.ReceiverVar}}.{{.FieldName}}
 }
-{{else if .MapType}}
-// Get{{.FieldName}} returns the {{.FieldName}} map if it's non-nil, an empty map otherwise.
+{{else if or .MapType .ArrayType }}
+// Get{{.FieldName}} returns the {{.FieldName}} {{if .MapType}}map{{else if .ArrayType }}slice{{end}} if it's non-nil, {{if .MapType}}an empty map{{else if .ArrayType }}nil{{end}} otherwise.
 func ({{.ReceiverVar}} *{{.ReceiverType}}) Get{{.FieldName}}() {{.FieldType}} {
   if {{.ReceiverVar}} == nil || {{.ReceiverVar}}.{{.FieldName}} == nil {
     return {{.ZeroValue}}
@@ -402,7 +423,7 @@ func Test{{.ReceiverType}}_Get{{.FieldName}}(tt *testing.T) {
   {{.ReceiverVar}} = nil
   {{.ReceiverVar}}.Get{{.FieldName}}()
 }
-{{else if .MapType}}
+{{else if or .MapType .ArrayType}}
 func Test{{.ReceiverType}}_Get{{.FieldName}}(tt *testing.T) {
   zeroValue := {{.FieldType}}{}
   {{.ReceiverVar}} := &{{.ReceiverType}}{ {{.FieldName}}: zeroValue }
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 91743340..6e5a089c 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -4614,6 +4614,78 @@ func (d *DeploymentEvent) GetSender() *User {
 	return d.Sender
 }
 
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (d *DeploymentProtectionRuleEvent) GetAction() string {
+	if d == nil || d.Action == nil {
+		return ""
+	}
+	return *d.Action
+}
+
+// GetDeployment returns the Deployment field.
+func (d *DeploymentProtectionRuleEvent) GetDeployment() *Deployment {
+	if d == nil {
+		return nil
+	}
+	return d.Deployment
+}
+
+// GetDeploymentCallbackURL returns the DeploymentCallbackURL field if it's non-nil, zero value otherwise.
+func (d *DeploymentProtectionRuleEvent) GetDeploymentCallbackURL() string {
+	if d == nil || d.DeploymentCallbackURL == nil {
+		return ""
+	}
+	return *d.DeploymentCallbackURL
+}
+
+// GetEnvironment returns the Environment field if it's non-nil, zero value otherwise.
+func (d *DeploymentProtectionRuleEvent) GetEnvironment() string {
+	if d == nil || d.Environment == nil {
+		return ""
+	}
+	return *d.Environment
+}
+
+// GetEvent returns the Event field if it's non-nil, zero value otherwise.
+func (d *DeploymentProtectionRuleEvent) GetEvent() string {
+	if d == nil || d.Event == nil {
+		return ""
+	}
+	return *d.Event
+}
+
+// GetInstallation returns the Installation field.
+func (d *DeploymentProtectionRuleEvent) GetInstallation() *Installation {
+	if d == nil {
+		return nil
+	}
+	return d.Installation
+}
+
+// GetOrganization returns the Organization field.
+func (d *DeploymentProtectionRuleEvent) GetOrganization() *Organization {
+	if d == nil {
+		return nil
+	}
+	return d.Organization
+}
+
+// GetRepo returns the Repo field.
+func (d *DeploymentProtectionRuleEvent) GetRepo() *Repository {
+	if d == nil {
+		return nil
+	}
+	return d.Repo
+}
+
+// GetSender returns the Sender field.
+func (d *DeploymentProtectionRuleEvent) GetSender() *User {
+	if d == nil {
+		return nil
+	}
+	return d.Sender
+}
+
 // GetAutoMerge returns the AutoMerge field if it's non-nil, zero value otherwise.
 func (d *DeploymentRequest) GetAutoMerge() bool {
 	if d == nil || d.AutoMerge == nil {
@@ -15054,6 +15126,14 @@ func (p *PushEvent) GetBefore() string {
 	return *p.Before
 }
 
+// GetCommits returns the Commits slice if it's non-nil, nil otherwise.
+func (p *PushEvent) GetCommits() []*HeadCommit {
+	if p == nil || p.Commits == nil {
+		return nil
+	}
+	return p.Commits
+}
+
 // GetCompare returns the Compare field if it's non-nil, zero value otherwise.
 func (p *PushEvent) GetCompare() string {
 	if p == nil || p.Compare == nil {
diff --git a/github/github.go b/github/github.go
index 2127a527..4170719a 100644
--- a/github/github.go
+++ b/github/github.go
@@ -40,6 +40,7 @@ const (
 	headerRateRemaining = "X-RateLimit-Remaining"
 	headerRateReset     = "X-RateLimit-Reset"
 	headerOTP           = "X-GitHub-OTP"
+	headerRetryAfter    = "Retry-After"
 
 	headerTokenExpiration = "GitHub-Authentication-Token-Expiration"
 
@@ -677,6 +678,30 @@ func parseRate(r *http.Response) Rate {
 	return rate
 }
 
+// parseSecondaryRate parses the secondary rate related headers,
+// and returns the time to retry after.
+func parseSecondaryRate(r *http.Response) *time.Duration {
+	// According to GitHub support, the "Retry-After" header value will be
+	// an integer which represents the number of seconds that one should
+	// wait before resuming making requests.
+	if v := r.Header.Get(headerRetryAfter); v != "" {
+		retryAfterSeconds, _ := strconv.ParseInt(v, 10, 64) // Error handling is noop.
+		retryAfter := time.Duration(retryAfterSeconds) * time.Second
+		return &retryAfter
+	}
+
+	// According to GitHub support, endpoints might return x-ratelimit-reset instead,
+	// as an integer which represents the number of seconds since epoch UTC,
+	// represting the time to resume making requests.
+	if v := r.Header.Get(headerRateReset); v != "" {
+		secondsSinceEpoch, _ := strconv.ParseInt(v, 10, 64) // Error handling is noop.
+		retryAfter := time.Until(time.Unix(secondsSinceEpoch, 0))
+		return &retryAfter
+	}
+
+	return nil
+}
+
 // parseTokenExpiration parses the TokenExpiration related headers.
 // Returns 0001-01-01 if the header is not defined or could not be parsed.
 func parseTokenExpiration(r *http.Response) Timestamp {
@@ -1156,13 +1181,8 @@ func CheckResponse(r *http.Response) error {
 			Response: errorResponse.Response,
 			Message:  errorResponse.Message,
 		}
-		if v := r.Header["Retry-After"]; len(v) > 0 {
-			// According to GitHub support, the "Retry-After" header value will be
-			// an integer which represents the number of seconds that one should
-			// wait before resuming making requests.
-			retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop.
-			retryAfter := time.Duration(retryAfterSeconds) * time.Second
-			abuseRateLimitError.RetryAfter = &retryAfter
+		if retryAfter := parseSecondaryRate(r); retryAfter != nil {
+			abuseRateLimitError.RetryAfter = retryAfter
 		}
 		return abuseRateLimitError
 	default:
diff --git a/github/messages.go b/github/messages.go
index 925e73e9..8547b881 100644
--- a/github/messages.go
+++ b/github/messages.go
@@ -55,6 +55,7 @@ var (
 		"deploy_key":                     "DeployKeyEvent",
 		"deployment":                     "DeploymentEvent",
 		"deployment_status":              "DeploymentStatusEvent",
+		"deployment_protection_rule":     "DeploymentProtectionRuleEvent",
 		"discussion":                     "DiscussionEvent",
 		"discussion_comment":             "DiscussionCommentEvent",
 		"fork":                           "ForkEvent",
diff --git a/github/orgs_members.go b/github/orgs_members.go
index 38f43bad..79f8a653 100644
--- a/github/orgs_members.go
+++ b/github/orgs_members.go
@@ -315,8 +315,8 @@ type CreateOrgInvitationOptions struct {
 	// * billing_manager - Non-owner organization members with ability to
 	//   manage the billing settings of your organization.
 	// Default is "direct_member".
-	Role   *string `json:"role"`
-	TeamID []int64 `json:"team_ids"`
+	Role   *string `json:"role,omitempty"`
+	TeamID []int64 `json:"team_ids,omitempty"`
 }
 
 // CreateOrgInvitation invites people to an organization by using their GitHub user ID or their email address.
diff --git a/go.mod b/go.mod
index e67d5b47..e6d69dd7 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 )
 
 require (
-	github.com/cloudflare/circl v1.1.0 // indirect
+	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	golang.org/x/crypto v0.7.0 // indirect
 	golang.org/x/net v0.9.0 // indirect
diff --git a/go.sum b/go.sum
index 4692d98b..a03fa655 100644
--- a/go.sum
+++ b/go.sum
@@ -1,39 +1,75 @@
+cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
-github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
+github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
+github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
 golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
 golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
diff --git a/scrape/go.mod b/scrape/go.mod
index 23cbb252..bf06bfa0 100644
--- a/scrape/go.mod
+++ b/scrape/go.mod
@@ -7,5 +7,5 @@ require (
 	github.com/google/go-cmp v0.5.9
 	github.com/google/go-github/v52 v52.0.0
 	github.com/xlzd/gotp v0.1.0
-	golang.org/x/net v0.9.0
+	golang.org/x/net v0.10.0
 )
diff --git a/scrape/go.sum b/scrape/go.sum
index 8f37dfe0..1739f99b 100644
--- a/scrape/go.sum
+++ b/scrape/go.sum
@@ -38,8 +38,9 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
 golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -54,13 +55,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
 golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=


Test patch:
diff --git a/github/event_types_test.go b/github/event_types_test.go
index cdbaee55..db284036 100644
--- a/github/event_types_test.go
+++ b/github/event_types_test.go
@@ -5483,6 +5483,391 @@ func TestDeploymentEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, u, want)
 }
 
+func TestDeploymentProtectionRuleEvent_Marshal(t *testing.T) {
+	testJSONMarshal(t, &DeploymentProtectionRuleEvent{}, "{}")
+
+	l := make(map[string]interface{})
+	l["key"] = "value"
+
+	jsonMsg, _ := json.Marshal(&l)
+
+	u := &DeploymentProtectionRuleEvent{
+		Action:                String("a"),
+		Environment:           String("e"),
+		DeploymentCallbackURL: String("b"),
+		Deployment: &Deployment{
+			URL:         String("url"),
+			ID:          Int64(1),
+			SHA:         String("sha"),
+			Ref:         String("ref"),
+			Task:        String("t"),
+			Payload:     jsonMsg,
+			Environment: String("e"),
+			Description: String("d"),
+			Creator: &User{
+				Login:     String("l"),
+				ID:        Int64(1),
+				NodeID:    String("n"),
+				URL:       String("u"),
+				ReposURL:  String("r"),
+				EventsURL: String("e"),
+				AvatarURL: String("a"),
+			},
+			CreatedAt:     &Timestamp{referenceTime},
+			UpdatedAt:     &Timestamp{referenceTime},
+			StatusesURL:   String("surl"),
+			RepositoryURL: String("rurl"),
+			NodeID:        String("nid"),
+		},
+		Repo: &Repository{
+			ID:   Int64(1),
+			URL:  String("s"),
+			Name: String("n"),
+		},
+		Organization: &Organization{
+			BillingEmail:                         String("be"),
+			Blog:                                 String("b"),
+			Company:                              String("c"),
+			Email:                                String("e"),
+			TwitterUsername:                      String("tu"),
+			Location:                             String("loc"),
+			Name:                                 String("n"),
+			Description:                          String("d"),
+			IsVerified:                           Bool(true),
+			HasOrganizationProjects:              Bool(true),
+			HasRepositoryProjects:                Bool(true),
+			DefaultRepoPermission:                String("drp"),
+			MembersCanCreateRepos:                Bool(true),
+			MembersCanCreateInternalRepos:        Bool(true),
+			MembersCanCreatePrivateRepos:         Bool(true),
+			MembersCanCreatePublicRepos:          Bool(false),
+			MembersAllowedRepositoryCreationType: String("marct"),
+			MembersCanCreatePages:                Bool(true),
+			MembersCanCreatePublicPages:          Bool(false),
+			MembersCanCreatePrivatePages:         Bool(true),
+		},
+		PullRequests: []*PullRequest{
+			{
+				URL:    String("u"),
+				ID:     Int64(1),
+				Number: Int(1),
+				Head: &PullRequestBranch{
+					Ref: String("r"),
+					SHA: String("s"),
+					Repo: &Repository{
+						ID:   Int64(1),
+						URL:  String("s"),
+						Name: String("n"),
+					},
+				},
+				Base: &PullRequestBranch{
+					Ref: String("r"),
+					SHA: String("s"),
+					Repo: &Repository{
+						ID:   Int64(1),
+						URL:  String("u"),
+						Name: String("n"),
+					},
+				},
+			},
+		},
+		Sender: &User{
+			Login:     String("l"),
+			ID:        Int64(1),
+			NodeID:    String("n"),
+			URL:       String("u"),
+			ReposURL:  String("r"),
+			EventsURL: String("e"),
+			AvatarURL: String("a"),
+		},
+		Installation: &Installation{
+			ID:       Int64(1),
+			NodeID:   String("nid"),
+			AppID:    Int64(1),
+			AppSlug:  String("as"),
+			TargetID: Int64(1),
+			Account: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			AccessTokensURL:     String("atu"),
+			RepositoriesURL:     String("ru"),
+			HTMLURL:             String("hu"),
+			TargetType:          String("tt"),
+			SingleFileName:      String("sfn"),
+			RepositorySelection: String("rs"),
+			Events:              []string{"e"},
+			SingleFilePaths:     []string{"s"},
+			Permissions: &InstallationPermissions{
+				Actions:                       String("a"),
+				Administration:                String("ad"),
+				Checks:                        String("c"),
+				Contents:                      String("co"),
+				ContentReferences:             String("cr"),
+				Deployments:                   String("d"),
+				Environments:                  String("e"),
+				Issues:                        String("i"),
+				Metadata:                      String("md"),
+				Members:                       String("m"),
+				OrganizationAdministration:    String("oa"),
+				OrganizationHooks:             String("oh"),
+				OrganizationPlan:              String("op"),
+				OrganizationPreReceiveHooks:   String("opr"),
+				OrganizationProjects:          String("op"),
+				OrganizationSecrets:           String("os"),
+				OrganizationSelfHostedRunners: String("osh"),
+				OrganizationUserBlocking:      String("oub"),
+				Packages:                      String("pkg"),
+				Pages:                         String("pg"),
+				PullRequests:                  String("pr"),
+				RepositoryHooks:               String("rh"),
+				RepositoryProjects:            String("rp"),
+				RepositoryPreReceiveHooks:     String("rprh"),
+				Secrets:                       String("s"),
+				SecretScanningAlerts:          String("ssa"),
+				SecurityEvents:                String("se"),
+				SingleFile:                    String("sf"),
+				Statuses:                      String("s"),
+				TeamDiscussions:               String("td"),
+				VulnerabilityAlerts:           String("va"),
+				Workflows:                     String("w"),
+			},
+			CreatedAt:              &Timestamp{referenceTime},
+			UpdatedAt:              &Timestamp{referenceTime},
+			HasMultipleSingleFiles: Bool(false),
+			SuspendedBy: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			SuspendedAt: &Timestamp{referenceTime},
+		},
+	}
+
+	want := `{
+		"action": "a",
+		"environment": "e",
+		"deployment_callback_url": "b",
+		"deployment": {
+			"url": "url",
+			"id": 1,
+			"sha": "sha",
+			"ref": "ref",
+			"task": "t",
+			"payload": {
+				"key": "value"
+			},
+			"environment": "e",
+			"description": "d",
+			"creator": {
+				"login": "l",
+				"id": 1,
+				"node_id": "n",
+				"avatar_url": "a",
+				"url": "u",
+				"events_url": "e",
+				"repos_url": "r"
+			},
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `,
+			"statuses_url": "surl",
+			"repository_url": "rurl",
+			"node_id": "nid"
+		},
+		"repository": {
+			"id": 1,
+			"name": "n",
+			"url": "s"
+		},
+		"organization": {
+			"name": "n",
+			"company": "c",
+			"blog": "b",
+			"location": "loc",
+			"email": "e",
+			"twitter_username": "tu",
+			"description": "d",
+			"billing_email": "be",
+			"is_verified": true,
+			"has_organization_projects": true,
+			"has_repository_projects": true,
+			"default_repository_permission": "drp",
+			"members_can_create_repositories": true,
+			"members_can_create_public_repositories": false,
+			"members_can_create_private_repositories": true,
+			"members_can_create_internal_repositories": true,
+			"members_allowed_repository_creation_type": "marct",
+			"members_can_create_pages": true,
+			"members_can_create_public_pages": false,
+			"members_can_create_private_pages": true
+		},
+		"pull_requests": [
+			{
+				"id": 1,
+				"number": 1,
+				"url": "u",
+				"head": {
+					"ref": "r",
+					"sha": "s",
+					"repo": {
+						"id": 1,
+						"name": "n",
+						"url": "s"
+					}
+				},
+				"base": {
+					"ref": "r",
+					"sha": "s",
+					"repo": {
+						"id": 1,
+						"name": "n",
+						"url": "u"
+					}
+				}
+			}
+		],
+		"sender": {
+			"login": "l",
+			"id": 1,
+			"node_id": "n",
+			"avatar_url": "a",
+			"url": "u",
+			"events_url": "e",
+			"repos_url": "r"
+		},
+		"installation": {
+			"id": 1,
+			"node_id": "nid",
+			"app_id": 1,
+			"app_slug": "as",
+			"target_id": 1,
+			"account": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"access_tokens_url": "atu",
+			"repositories_url": "ru",
+			"html_url": "hu",
+			"target_type": "tt",
+			"single_file_name": "sfn",
+			"repository_selection": "rs",
+			"events": [
+				"e"
+			],
+			"single_file_paths": [
+				"s"
+			],
+			"permissions": {
+				"actions": "a",
+				"administration": "ad",
+				"checks": "c",
+				"contents": "co",
+				"content_references": "cr",
+				"deployments": "d",
+				"environments": "e",
+				"issues": "i",
+				"metadata": "md",
+				"members": "m",
+				"organization_administration": "oa",
+				"organization_hooks": "oh",
+				"organization_plan": "op",
+				"organization_pre_receive_hooks": "opr",
+				"organization_projects": "op",
+				"organization_secrets": "os",
+				"organization_self_hosted_runners": "osh",
+				"organization_user_blocking": "oub",
+				"packages": "pkg",
+				"pages": "pg",
+				"pull_requests": "pr",
+				"repository_hooks": "rh",
+				"repository_projects": "rp",
+				"repository_pre_receive_hooks": "rprh",
+				"secrets": "s",
+				"secret_scanning_alerts": "ssa",
+				"security_events": "se",
+				"single_file": "sf",
+				"statuses": "s",
+				"team_discussions": "td",
+				"vulnerability_alerts": "va",
+				"workflows": "w"
+			},
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `,
+			"has_multiple_single_files": false,
+			"suspended_by": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"suspended_at": ` + referenceTimeStr + `
+		}
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
 func TestDeploymentStatusEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, &DeploymentStatusEvent{}, "{}")
 
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 8f7292d7..8d6c6a4d 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -5399,6 +5399,81 @@ func TestDeploymentEvent_GetSender(tt *testing.T) {
 	d.GetSender()
 }
 
+func TestDeploymentProtectionRuleEvent_GetAction(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentProtectionRuleEvent{Action: &zeroValue}
+	d.GetAction()
+	d = &DeploymentProtectionRuleEvent{}
+	d.GetAction()
+	d = nil
+	d.GetAction()
+}
+
+func TestDeploymentProtectionRuleEvent_GetDeployment(tt *testing.T) {
+	d := &DeploymentProtectionRuleEvent{}
+	d.GetDeployment()
+	d = nil
+	d.GetDeployment()
+}
+
+func TestDeploymentProtectionRuleEvent_GetDeploymentCallbackURL(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentProtectionRuleEvent{DeploymentCallbackURL: &zeroValue}
+	d.GetDeploymentCallbackURL()
+	d = &DeploymentProtectionRuleEvent{}
+	d.GetDeploymentCallbackURL()
+	d = nil
+	d.GetDeploymentCallbackURL()
+}
+
+func TestDeploymentProtectionRuleEvent_GetEnvironment(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentProtectionRuleEvent{Environment: &zeroValue}
+	d.GetEnvironment()
+	d = &DeploymentProtectionRuleEvent{}
+	d.GetEnvironment()
+	d = nil
+	d.GetEnvironment()
+}
+
+func TestDeploymentProtectionRuleEvent_GetEvent(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentProtectionRuleEvent{Event: &zeroValue}
+	d.GetEvent()
+	d = &DeploymentProtectionRuleEvent{}
+	d.GetEvent()
+	d = nil
+	d.GetEvent()
+}
+
+func TestDeploymentProtectionRuleEvent_GetInstallation(tt *testing.T) {
+	d := &DeploymentProtectionRuleEvent{}
+	d.GetInstallation()
+	d = nil
+	d.GetInstallation()
+}
+
+func TestDeploymentProtectionRuleEvent_GetOrganization(tt *testing.T) {
+	d := &DeploymentProtectionRuleEvent{}
+	d.GetOrganization()
+	d = nil
+	d.GetOrganization()
+}
+
+func TestDeploymentProtectionRuleEvent_GetRepo(tt *testing.T) {
+	d := &DeploymentProtectionRuleEvent{}
+	d.GetRepo()
+	d = nil
+	d.GetRepo()
+}
+
+func TestDeploymentProtectionRuleEvent_GetSender(tt *testing.T) {
+	d := &DeploymentProtectionRuleEvent{}
+	d.GetSender()
+	d = nil
+	d.GetSender()
+}
+
 func TestDeploymentRequest_GetAutoMerge(tt *testing.T) {
 	var zeroValue bool
 	d := &DeploymentRequest{AutoMerge: &zeroValue}
@@ -17477,6 +17552,16 @@ func TestPushEvent_GetBefore(tt *testing.T) {
 	p.GetBefore()
 }
 
+func TestPushEvent_GetCommits(tt *testing.T) {
+	zeroValue := []*HeadCommit{}
+	p := &PushEvent{Commits: zeroValue}
+	p.GetCommits()
+	p = &PushEvent{}
+	p.GetCommits()
+	p = nil
+	p.GetCommits()
+}
+
 func TestPushEvent_GetCompare(tt *testing.T) {
 	var zeroValue string
 	p := &PushEvent{Compare: &zeroValue}
diff --git a/github/github_test.go b/github/github_test.go
index 9c40728f..2f92d3ba 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -17,6 +17,7 @@ import (
 	"os"
 	"path"
 	"reflect"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -1475,14 +1476,14 @@ func TestDo_rateLimit_abuseRateLimitErrorEnterprise(t *testing.T) {
 	}
 }
 
-// Ensure *AbuseRateLimitError.RetryAfter is parsed correctly.
+// Ensure *AbuseRateLimitError.RetryAfter is parsed correctly for the Retry-After header.
 func TestDo_rateLimit_abuseRateLimitError_retryAfter(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
 	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
-		w.Header().Set("Retry-After", "123") // Retry after value of 123 seconds.
+		w.Header().Set(headerRetryAfter, "123") // Retry after value of 123 seconds.
 		w.WriteHeader(http.StatusForbidden)
 		fmt.Fprintln(w, `{
    "message": "You have triggered an abuse detection mechanism ...",
@@ -1528,6 +1529,64 @@ func TestDo_rateLimit_abuseRateLimitError_retryAfter(t *testing.T) {
 	}
 }
 
+// Ensure *AbuseRateLimitError.RetryAfter is parsed correctly for the x-ratelimit-reset header.
+func TestDo_rateLimit_abuseRateLimitError_xRateLimitReset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	// x-ratelimit-reset value of 123 seconds into the future.
+	blockUntil := time.Now().Add(time.Duration(123) * time.Second).Unix()
+
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		w.Header().Set(headerRateReset, strconv.Itoa(int(blockUntil)))
+		w.Header().Set(headerRateRemaining, "1") // set remaining to a value > 0 to distinct from a primary rate limit
+		w.WriteHeader(http.StatusForbidden)
+		fmt.Fprintln(w, `{
+   "message": "You have triggered an abuse detection mechanism ...",
+   "documentation_url": "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits"
+}`)
+	})
+
+	req, _ := client.NewRequest("GET", ".", nil)
+	ctx := context.Background()
+	_, err := client.Do(ctx, req, nil)
+
+	if err == nil {
+		t.Error("Expected error to be returned.")
+	}
+	abuseRateLimitErr, ok := err.(*AbuseRateLimitError)
+	if !ok {
+		t.Fatalf("Expected a *AbuseRateLimitError error; got %#v.", err)
+	}
+	if abuseRateLimitErr.RetryAfter == nil {
+		t.Fatalf("abuseRateLimitErr RetryAfter is nil, expected not-nil")
+	}
+	// the retry after value might be a bit smaller than the original duration because the duration is calculated from the expected end-of-cooldown time
+	if got, want := *abuseRateLimitErr.RetryAfter, 123*time.Second; want-got > 1*time.Second {
+		t.Errorf("abuseRateLimitErr RetryAfter = %v, want %v", got, want)
+	}
+
+	// expect prevention of a following request
+	if _, err = client.Do(ctx, req, nil); err == nil {
+		t.Error("Expected error to be returned.")
+	}
+	abuseRateLimitErr, ok = err.(*AbuseRateLimitError)
+	if !ok {
+		t.Fatalf("Expected a *AbuseRateLimitError error; got %#v.", err)
+	}
+	if abuseRateLimitErr.RetryAfter == nil {
+		t.Fatalf("abuseRateLimitErr RetryAfter is nil, expected not-nil")
+	}
+	// the saved duration might be a bit smaller than Retry-After because the duration is calculated from the expected end-of-cooldown time
+	if got, want := *abuseRateLimitErr.RetryAfter, 123*time.Second; want-got > 1*time.Second {
+		t.Errorf("abuseRateLimitErr RetryAfter = %v, want %v", got, want)
+	}
+	if got, wantSuffix := abuseRateLimitErr.Message, "not making remote request."; !strings.HasSuffix(got, wantSuffix) {
+		t.Errorf("Expected request to be prevented because of secondary rate limit, got: %v.", got)
+	}
+}
+
 func TestDo_noContent(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/messages_test.go b/github/messages_test.go
index 04f29420..8dbb0fdf 100644
--- a/github/messages_test.go
+++ b/github/messages_test.go
@@ -292,6 +292,10 @@ func TestParseWebHook(t *testing.T) {
 			payload:     &DeploymentEvent{},
 			messageType: "deployment",
 		},
+		{
+			payload:     &DeploymentProtectionRuleEvent{},
+			messageType: "deployment_protection_rule",
+		},
 		{
 			payload:     &DeploymentStatusEvent{},
 			messageType: "deployment_status",