Repository: google/go-github
Pull request #2031 (2021-08-05 16:51:36)

Add TokenExpiration to Response
GitHub has been updated to allow you to set the expiration date of your personal access token. And the expiration date is now displayed in the `GitHub-Authentication-Token-Expiration` header of the GItHub API. 
https://github.blog/changelog/2021-07-26-expiration-options-for-personal-access-tokens/

This PR add `TokenExpiration` field to Response.

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/2031?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2031](https://codecov.io/gh/google/go-github/pull/2031?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (6913685) into [master](https://codecov.io/gh/google/go-github/commit/d8a66d0c4de722a31305a9d9cdbfcd8d3aafee3a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (d8a66d0) will **decrease** coverage by `0.04%`.
> The diff coverage is `50.00%`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-github/pull/2031/graphs/tree.svg?width=650&height=150&src=pr&token=EgYjGjZlI0&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)](https://codecov.io/gh/google/go-github/pull/2031?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

```diff
@@            Coverage Diff             @@
##           master    #2031      +/-   ##
==========================================
- Coverage   97.86%   97.82%   -0.05%     
==========================================
  Files         107      107              
  Lines        6901     6907       +6     
==========================================
+ Hits         6754     6757       +3     
- Misses         81       83       +2     
- Partials       66       67       +1     
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/2031?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/github.go](https://codecov.io/gh/google/go-github/pull/2031/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2dpdGh1Yi5nbw==) | `96.37% <50.00%> (-0.74%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/2031?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-github/pull/2031?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Last update [d8a66d0...6913685](https://codecov.io/gh/google/go-github/pull/2031?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).

w-haibara: Hi, @gmlewis ! Thanks for the thoughtful review.
I've fixed the areas you commented on and added a test. I apologize again for the unformatted commits. I've fixed that as well.

w-haibara: Thank you, @gmlewis.
This is almost the first OSS contributor for me. Your review has helped me a lot!
gmlewis: > Thank you, @gmlewis.
> This is almost the first OSS contributor for me. Your review has helped me a lot!

Thank you, for your contributions, @w-haibara !  Your help is greatly appreciated!

Patch:
diff --git a/example/tokenauth/main.go b/example/tokenauth/main.go
index 7f172965..67064fa3 100644
--- a/example/tokenauth/main.go
+++ b/example/tokenauth/main.go
@@ -22,6 +22,7 @@ import (
 func main() {
 	fmt.Print("GitHub Token: ")
 	byteToken, _ := terminal.ReadPassword(int(syscall.Stdin))
+	println()
 	token := string(byteToken)
 
 	ctx := context.Background()
@@ -39,7 +40,12 @@ func main() {
 	}
 
 	// Rate.Limit should most likely be 5000 when authorized.
-	log.Printf("Rate: %#v", resp.Rate)
+	log.Printf("Rate: %#v\n", resp.Rate)
+
+	// If a Token Expiration has been set, it will be displayed.
+	if !resp.TokenExpiration.IsZero() {
+		log.Printf("Token Expiration: %v\n", resp.TokenExpiration)
+	}
 
 	fmt.Printf("\n%v\n", github.Stringify(user))
 }
diff --git a/github/code-scanning.go b/github/code-scanning.go
index 640ec899..dbaeb346 100644
--- a/github/code-scanning.go
+++ b/github/code-scanning.go
@@ -20,13 +20,14 @@ type CodeScanningService service
 
 // Rule represents the complete details of GitHub Code Scanning alert type.
 type Rule struct {
-	ID              *string  `json:"id,omitempty"`
-	Severity        *string  `json:"severity,omitempty"`
-	Description     *string  `json:"description,omitempty"`
-	Name            *string  `json:"name,omitempty"`
-	FullDescription *string  `json:"full_description,omitempty"`
-	Tags            []string `json:"tags,omitempty"`
-	Help            *string  `json:"help,omitempty"`
+	ID                    *string  `json:"id,omitempty"`
+	Severity              *string  `json:"severity,omitempty"`
+	Description           *string  `json:"description,omitempty"`
+	Name                  *string  `json:"name,omitempty"`
+	SecuritySeverityLevel *string  `json:"security_severity_level,omitempty"`
+	FullDescription       *string  `json:"full_description,omitempty"`
+	Tags                  []string `json:"tags,omitempty"`
+	Help                  *string  `json:"help,omitempty"`
 }
 
 // Location represents the exact location of the GitHub Code Scanning Alert in the scanned project.
@@ -72,7 +73,7 @@ type Alert struct {
 	Rule               *Rule               `json:"rule,omitempty"`
 	Tool               *Tool               `json:"tool,omitempty"`
 	CreatedAt          *Timestamp          `json:"created_at,omitempty"`
-	Open               *bool               `json:"open,omitempty"`
+	State              *string             `json:"state,omitempty"`
 	ClosedBy           *User               `json:"closed_by,omitempty"`
 	ClosedAt           *Timestamp          `json:"closed_at,omitempty"`
 	URL                *string             `json:"url,omitempty"`
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 3fa1e018..37a22830 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -220,14 +220,6 @@ func (a *Alert) GetMostRecentInstance() *MostRecentInstance {
 	return a.MostRecentInstance
 }
 
-// GetOpen returns the Open field if it's non-nil, zero value otherwise.
-func (a *Alert) GetOpen() bool {
-	if a == nil || a.Open == nil {
-		return false
-	}
-	return *a.Open
-}
-
 // GetRule returns the Rule field.
 func (a *Alert) GetRule() *Rule {
 	if a == nil {
@@ -260,6 +252,14 @@ func (a *Alert) GetRuleSeverity() string {
 	return *a.RuleSeverity
 }
 
+// GetState returns the State field if it's non-nil, zero value otherwise.
+func (a *Alert) GetState() string {
+	if a == nil || a.State == nil {
+		return ""
+	}
+	return *a.State
+}
+
 // GetTool returns the Tool field.
 func (a *Alert) GetTool() *Tool {
 	if a == nil {
@@ -14372,6 +14372,14 @@ func (r *Rule) GetName() string {
 	return *r.Name
 }
 
+// GetSecuritySeverityLevel returns the SecuritySeverityLevel field if it's non-nil, zero value otherwise.
+func (r *Rule) GetSecuritySeverityLevel() string {
+	if r == nil || r.SecuritySeverityLevel == nil {
+		return ""
+	}
+	return *r.SecuritySeverityLevel
+}
+
 // GetSeverity returns the Severity field if it's non-nil, zero value otherwise.
 func (r *Rule) GetSeverity() string {
 	if r == nil || r.Severity == nil {
diff --git a/github/github.go b/github/github.go
index 2f765365..fe686857 100644
--- a/github/github.go
+++ b/github/github.go
@@ -37,6 +37,8 @@ const (
 	headerRateReset     = "X-RateLimit-Reset"
 	headerOTP           = "X-GitHub-OTP"
 
+	headerTokenExpiration = "GitHub-Authentication-Token-Expiration"
+
 	mediaTypeV3                = "application/vnd.github.v3+json"
 	defaultMediaType           = "application/octet-stream"
 	mediaTypeV3SHA             = "application/vnd.github.v3.sha"
@@ -464,6 +466,9 @@ type Response struct {
 	// Explicitly specify the Rate type so Rate's String() receiver doesn't
 	// propagate to Response.
 	Rate Rate
+
+	// token's expiration date
+	TokenExpiration Timestamp
 }
 
 // newResponse creates a new Response for the provided http.Response.
@@ -472,6 +477,7 @@ func newResponse(r *http.Response) *Response {
 	response := &Response{Response: r}
 	response.populatePageValues()
 	response.Rate = parseRate(r)
+	response.TokenExpiration = parseTokenExpiration(r)
 	return response
 }
 
@@ -551,6 +557,17 @@ func parseRate(r *http.Response) Rate {
 	return rate
 }
 
+// parseTokenExpiration parses the TokenExpiration related headers.
+func parseTokenExpiration(r *http.Response) Timestamp {
+	var exp Timestamp
+	if v := r.Header.Get(headerTokenExpiration); v != "" {
+		if t, err := time.Parse("2006-01-02 03:04:05 MST", v); err == nil {
+			exp = Timestamp{t.Local()}
+		}
+	}
+	return exp
+}
+
 type requestContext uint8
 
 const (


Test patch:
diff --git a/github/code-scanning_test.go b/github/code-scanning_test.go
index 7403582f..6ef8d84a 100644
--- a/github/code-scanning_test.go
+++ b/github/code-scanning_test.go
@@ -96,7 +96,7 @@ func TestActionsService_ListAlertsForRepo(t *testing.T) {
 					]
 				},
 				"created_at":"2020-05-06T12:00:00Z",
-				"open":true,
+				"state":"open",
 				"closed_by":null,
 				"closed_at":null,
 				"url":"https://api.github.com/repos/o/r/code-scanning/alerts/25",
@@ -138,7 +138,7 @@ func TestActionsService_ListAlertsForRepo(t *testing.T) {
 					]
 				},
 				"created_at":"2020-05-06T12:00:00Z",
-				"open":true,
+				"state":"open",
 				"closed_by":null,
 				"closed_at":null,
 				"url":"https://api.github.com/repos/o/r/code-scanning/alerts/88",
@@ -169,7 +169,7 @@ func TestActionsService_ListAlertsForRepo(t *testing.T) {
 				Help:            String("Expression has no effect"),
 			},
 			CreatedAt: &date,
-			Open:      Bool(true),
+			State:     String("open"),
 			ClosedBy:  nil,
 			ClosedAt:  nil,
 			URL:       String("https://api.github.com/repos/o/r/code-scanning/alerts/25"),
@@ -205,7 +205,7 @@ func TestActionsService_ListAlertsForRepo(t *testing.T) {
 				Help:            String("Expression has no effect"),
 			},
 			CreatedAt: &date,
-			Open:      Bool(true),
+			State:     String("open"),
 			ClosedBy:  nil,
 			ClosedAt:  nil,
 			URL:       String("https://api.github.com/repos/o/r/code-scanning/alerts/88"),
@@ -288,7 +288,7 @@ func TestActionsService_GetAlert(t *testing.T) {
 					]
 				},      
 				"created_at":"2019-01-02T15:04:05Z",
-				"open":true,
+				"state":"open",
 				"closed_by":null,
 				"closed_at":null,
 				"url":"https://api.github.com/repos/o/r/code-scanning/alerts/88",
@@ -316,7 +316,7 @@ func TestActionsService_GetAlert(t *testing.T) {
 			Help:            String("Expression has no effect"),
 		},
 		CreatedAt: &date,
-		Open:      Bool(true),
+		State:     String("open"),
 		ClosedBy:  nil,
 		ClosedAt:  nil,
 		URL:       String("https://api.github.com/repos/o/r/code-scanning/alerts/88"),
@@ -370,7 +370,7 @@ func TestAlert_Marshal(t *testing.T) {
 			Version: String("v"),
 		},
 		CreatedAt: &Timestamp{referenceTime},
-		Open:      Bool(false),
+		State:     String("fixed"),
 		ClosedBy: &User{
 			Login:     String("l"),
 			ID:        Int64(1),
@@ -395,7 +395,7 @@ func TestAlert_Marshal(t *testing.T) {
 			"version": "v"
 		},
 		"created_at": ` + referenceTimeStr + `,
-		"open": false,
+		"state": "fixed",
 		"closed_by": {
 			"login": "l",
 			"id": 1,
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index b3ef053f..04bf837a 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -234,16 +234,6 @@ func TestAlert_GetMostRecentInstance(tt *testing.T) {
 	a.GetMostRecentInstance()
 }
 
-func TestAlert_GetOpen(tt *testing.T) {
-	var zeroValue bool
-	a := &Alert{Open: &zeroValue}
-	a.GetOpen()
-	a = &Alert{}
-	a.GetOpen()
-	a = nil
-	a.GetOpen()
-}
-
 func TestAlert_GetRule(tt *testing.T) {
 	a := &Alert{}
 	a.GetRule()
@@ -281,6 +271,16 @@ func TestAlert_GetRuleSeverity(tt *testing.T) {
 	a.GetRuleSeverity()
 }
 
+func TestAlert_GetState(tt *testing.T) {
+	var zeroValue string
+	a := &Alert{State: &zeroValue}
+	a.GetState()
+	a = &Alert{}
+	a.GetState()
+	a = nil
+	a.GetState()
+}
+
 func TestAlert_GetTool(tt *testing.T) {
 	a := &Alert{}
 	a.GetTool()
@@ -16796,6 +16796,16 @@ func TestRule_GetName(tt *testing.T) {
 	r.GetName()
 }
 
+func TestRule_GetSecuritySeverityLevel(tt *testing.T) {
+	var zeroValue string
+	r := &Rule{SecuritySeverityLevel: &zeroValue}
+	r.GetSecuritySeverityLevel()
+	r = &Rule{}
+	r.GetSecuritySeverityLevel()
+	r = nil
+	r.GetSecuritySeverityLevel()
+}
+
 func TestRule_GetSeverity(tt *testing.T) {
 	var zeroValue string
 	r := &Rule{Severity: &zeroValue}
diff --git a/github/github_test.go b/github/github_test.go
index d2240558..666bb45c 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -2206,3 +2206,36 @@ func TestRateLimits_Marshal(t *testing.T) {
 
 	testJSONMarshal(t, u, want)
 }
+
+func TestParseTokenExpiration(t *testing.T) {
+	tests := []struct {
+		header string
+		want   Timestamp
+	}{
+		{
+			header: "",
+			want:   Timestamp{},
+		},
+		{
+			header: "this is a garbage",
+			want:   Timestamp{},
+		},
+		{
+			header: "2021-09-03 02:34:04 UTC",
+			want:   Timestamp{time.Date(2021, time.September, 3, 2, 34, 4, 0, time.UTC)},
+		},
+	}
+
+	for _, tt := range tests {
+		res := &http.Response{
+			Request: &http.Request{},
+			Header:  http.Header{},
+		}
+
+		res.Header.Set(headerTokenExpiration, tt.header)
+		exp := parseTokenExpiration(res)
+		if !exp.Equal(tt.want) {
+			t.Errorf("parseTokenExpiration returned %#v, want %#v", exp, tt.want)
+		}
+	}
+}