Repository: zitadel/oidc
Pull request #194 (2022-07-13 23:37:03)

feat: Add db scanner methods for SpaceDelimitedArray
I've added methods to `SpaceDelimitedArray` to allow it t be easily marshaled and unmarshaled via SQL.

I also added an array of the GrantTypes to help with the same.   This code is not yet in production on my side.

See https://husobee.github.io/golang/database/2015/06/12/scanner-valuer.html for a discussion of Scan/Value.

It would also be helpful to use an enumer (eg https://github.com/alvaroloes/enumer) to provide enum <-> string transformations for the various types that are defined with `iota` like `op.AccessTokenType` and `op.ApplicationType` that are required for third parties to implement some of the required interfaces.   There is positive feedback to for the idea of adding an enumer, I'm happy to do so, but I didn't want to assume that would be well received. 

codecov[bot]: # [Codecov](https://codecov.io/gh/zitadel/oidc/pull/194?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel) Report
> Merging [#194](https://codecov.io/gh/zitadel/oidc/pull/194?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel) (5506d70) into [main](https://codecov.io/gh/zitadel/oidc/commit/aea3f4326805fba6cc5b62b6a05d8d2d97fee1d2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel) (aea3f43) will **increase** coverage by `0.59%`.
> The diff coverage is `91.30%`.

```diff
@@            Coverage Diff             @@
##             main     #194      +/-   ##
==========================================
+ Coverage   12.74%   13.33%   +0.59%     
==========================================
  Files          40       40              
  Lines        3029     3052      +23     
==========================================
+ Hits          386      407      +21     
- Misses       2632     2634       +2     
  Partials       11       11              
```


| [Impacted Files](https://codecov.io/gh/zitadel/oidc/pull/194?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel) | Coverage Δ | |
|---|---|---|
| [pkg/oidc/token\_request.go](https://codecov.io/gh/zitadel/oidc/pull/194/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel#diff-cGtnL29pZGMvdG9rZW5fcmVxdWVzdC5nbw==) | `0.00% <ø> (ø)` | |
| [pkg/oidc/types.go](https://codecov.io/gh/zitadel/oidc/pull/194/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel#diff-cGtnL29pZGMvdHlwZXMuZ28=) | `65.85% <91.30%> (+9.92%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/zitadel/oidc/pull/194?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel).
> **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=zitadel)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/zitadel/oidc/pull/194?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel). Last update [aea3f43...5506d70](https://codecov.io/gh/zitadel/oidc/pull/194?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zitadel). 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=zitadel).


Patch:
diff --git a/README.md b/README.md
index 1c9e30d..872b9b6 100644
--- a/README.md
+++ b/README.md
@@ -113,7 +113,7 @@ We did not choose `fosite` because it implements `OAuth 2.0` on its own and does
 
 ## License
 
-The full functionality of this library is and stays open source and free to use for everyone. Visit our [website](https://caos.ch) and get in touch.
+The full functionality of this library is and stays open source and free to use for everyone. Visit our [website](https://zitadel.com) and get in touch.
 
 See the exact licensing terms [here](./LICENSE)
 
diff --git a/go.mod b/go.mod
index 81b6217..0ed79a6 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/gorilla/schema v1.2.0
 	github.com/gorilla/securecookie v1.1.1
 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
-	github.com/sirupsen/logrus v1.8.1
+	github.com/sirupsen/logrus v1.9.0
 	github.com/stretchr/testify v1.8.0
 	github.com/zitadel/logging v0.3.3
 	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
diff --git a/go.sum b/go.sum
index 8146afe..87b2fc1 100644
--- a/go.sum
+++ b/go.sum
@@ -131,8 +131,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -262,8 +263,9 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220207234003-57398862261d h1:Bm7BNOQt2Qv7ZqysjeLjgCBanX+88Z/OtdvsrEv1Djc=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/pkg/oidc/token_request.go b/pkg/oidc/token_request.go
index 56c4f1c..989e792 100644
--- a/pkg/oidc/token_request.go
+++ b/pkg/oidc/token_request.go
@@ -32,6 +32,11 @@ const (
 	ClientAssertionTypeJWTAssertion = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
 )
 
+var AllGrantTypes = []GrantType{
+	GrantTypeCode, GrantTypeRefreshToken, GrantTypeClientCredentials,
+	GrantTypeBearer, GrantTypeTokenExchange, GrantTypeImplicit,
+	ClientAssertionTypeJWTAssertion}
+
 type GrantType string
 
 type TokenRequest interface {
diff --git a/pkg/oidc/types.go b/pkg/oidc/types.go
index b6a75f4..1260798 100644
--- a/pkg/oidc/types.go
+++ b/pkg/oidc/types.go
@@ -1,7 +1,9 @@
 package oidc
 
 import (
+	"database/sql/driver"
 	"encoding/json"
+	"fmt"
 	"strings"
 	"time"
 
@@ -95,6 +97,34 @@ func (s *SpaceDelimitedArray) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+func (s *SpaceDelimitedArray) Scan(src interface{}) error {
+	if src == nil {
+		*s = nil
+		return nil
+	}
+	switch v := src.(type) {
+	case string:
+		if len(v) == 0 {
+			*s = SpaceDelimitedArray{}
+			return nil
+		}
+		*s = strings.Split(v, " ")
+	case []byte:
+		if len(v) == 0 {
+			*s = SpaceDelimitedArray{}
+			return nil
+		}
+		*s = strings.Split(string(v), " ")
+	default:
+		return fmt.Errorf("cannot convert %T to SpaceDelimitedArray", src)
+	}
+	return nil
+}
+
+func (s SpaceDelimitedArray) Value() (driver.Value, error) {
+	return strings.Join(s, " "), nil
+}
+
 type Time time.Time
 
 func (t *Time) UnmarshalJSON(data []byte) error {


Test patch:
diff --git a/pkg/oidc/types_test.go b/pkg/oidc/types_test.go
index c03a775..6c62c40 100644
--- a/pkg/oidc/types_test.go
+++ b/pkg/oidc/types_test.go
@@ -3,6 +3,8 @@ package oidc
 import (
 	"bytes"
 	"encoding/json"
+	"strconv"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -228,6 +230,7 @@ func TestScopes_UnmarshalText(t *testing.T) {
 		})
 	}
 }
+
 func TestScopes_MarshalText(t *testing.T) {
 	type args struct {
 		scopes SpaceDelimitedArray
@@ -294,3 +297,41 @@ func TestScopes_MarshalText(t *testing.T) {
 		})
 	}
 }
+
+func TestSpaceDelimitatedArray_ValuerNotNil(t *testing.T) {
+	inputs := [][]string{
+		{"two", "elements"},
+		{"one"},
+		{ /*zero*/ },
+	}
+	for _, input := range inputs {
+		t.Run(strconv.Itoa(len(input))+strings.Join(input, "_"), func(t *testing.T) {
+			sda := SpaceDelimitedArray(input)
+			dbValue, err := sda.Value()
+			if !assert.NoError(t, err, "Value") {
+				return
+			}
+			var reversed SpaceDelimitedArray
+			err = reversed.Scan(dbValue)
+			if assert.NoError(t, err, "Scan string") {
+				assert.Equal(t, sda, reversed, "scan string")
+			}
+			reversed = nil
+			dbValueString, ok := dbValue.(string)
+			if assert.True(t, ok, "dbValue is string") {
+				err = reversed.Scan([]byte(dbValueString))
+				if assert.NoError(t, err, "Scan bytes") {
+					assert.Equal(t, sda, reversed, "scan bytes")
+				}
+			}
+		})
+	}
+}
+
+func TestSpaceDelimitatedArray_ValuerNil(t *testing.T) {
+	var reversed SpaceDelimitedArray
+	err := reversed.Scan(nil)
+	if assert.NoError(t, err, "Scan nil") {
+		assert.Equal(t, SpaceDelimitedArray(nil), reversed, "scan nil")
+	}
+}