Skip to content

Commit

Permalink
feat: adjust pagination for schemas endpoints
Browse files Browse the repository at this point in the history
closes #44
  • Loading branch information
shipperizer committed Apr 5, 2024
1 parent f596fbe commit 018774c
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 103 deletions.
7 changes: 6 additions & 1 deletion pkg/schemas/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (a *API) handleList(w http.ResponseWriter, r *http.Request) {

pagination := types.ParsePagination(r.URL.Query())

schemas, err := a.service.ListSchemas(r.Context(), pagination.Page, pagination.Size)
schemas, err := a.service.ListSchemas(r.Context(), pagination.Size, pagination.PageToken)

if err != nil {
rr := a.error(schemas.Error)
Expand All @@ -63,6 +63,11 @@ func (a *API) handleList(w http.ResponseWriter, r *http.Request) {
return
}

// TODO @shipperizer improve on this, see if better to stick with link headers
pagination.Next = schemas.Tokens.Next
pagination.Prev = schemas.Tokens.Prev
pagination.First = schemas.Tokens.First

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(
types.Response{
Expand Down
11 changes: 8 additions & 3 deletions pkg/schemas/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
//go:generate mockgen -build_flags=--mod=mod -package schemas -destination ./mock_monitor.go -source=../../internal/monitoring/interfaces.go
//go:generate mockgen -build_flags=--mod=mod -package schemas -destination ./mock_tracing.go go.opentelemetry.io/otel/trace Tracer
//go:generate mockgen -build_flags=--mod=mod -package schemas -destination ./mock_corev1.go k8s.io/client-go/kubernetes/typed/core/v1 CoreV1Interface,ConfigMapInterface
//go:generate mockgen -build_flags=--mod=mod -package schemas -destination ./mock_kratos.go github.com/ory/kratos-client-go IdentityApi
//go:generate mockgen -build_flags=--mod=mod -package schemas -destination ./mock_kratos.go github.com/ory/kratos-client-go IdentityAPI

func TestHandleListSuccess(t *testing.T) {
ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -102,11 +102,16 @@ func TestHandleListSuccess(t *testing.T) {
Schema: v1Schema,
},
},
Tokens: PaginationTokens{
Next: "eyJvZmZzZXQiOiIyNTAiLCJ2IjoyfQ",
First: "eyJvZmZzZXQiOiIwIiwidiI6Mn0",
Prev: "eyJvZmZzZXQiOiItMjUwIiwidiI6Mn0",
},
}

req := httptest.NewRequest(http.MethodGet, "/api/v0/schemas", nil)

mockService.EXPECT().ListSchemas(gomock.Any(), int64(1), int64(100)).Return(&schemas, nil)
mockService.EXPECT().ListSchemas(gomock.Any(), int64(100), "").Return(&schemas, nil)

w := httptest.NewRecorder()
mux := chi.NewMux()
Expand Down Expand Up @@ -161,7 +166,7 @@ func TestHandleListFails(t *testing.T) {
gerr.SetMessage("teapot error")
gerr.SetReason("teapot is broken")

mockService.EXPECT().ListSchemas(gomock.Any(), int64(1), int64(100)).Return(&IdentitySchemaData{IdentitySchemas: make([]kClient.IdentitySchemaContainer, 0), Error: gerr}, fmt.Errorf("error"))
mockService.EXPECT().ListSchemas(gomock.Any(), int64(100), "").Return(&IdentitySchemaData{IdentitySchemas: make([]kClient.IdentitySchemaContainer, 0), Error: gerr}, fmt.Errorf("error"))

w := httptest.NewRecorder()
mux := chi.NewMux()
Expand Down
2 changes: 1 addition & 1 deletion pkg/schemas/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

type ServiceInterface interface {
ListSchemas(context.Context, int64, int64) (*IdentitySchemaData, error)
ListSchemas(context.Context, int64, string) (*IdentitySchemaData, error)
GetSchema(context.Context, string) (*IdentitySchemaData, error)
EditSchema(context.Context, string, *kClient.IdentitySchemaContainer) (*IdentitySchemaData, error)
CreateSchema(context.Context, *kClient.IdentitySchemaContainer) (*IdentitySchemaData, error)
Expand Down
51 changes: 46 additions & 5 deletions pkg/schemas/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
"fmt"
"io"
"net/http"
"net/url"

"github.com/google/uuid"
kClient "github.com/ory/kratos-client-go"
"github.com/tomnomnom/linkheader"
"go.opentelemetry.io/otel/trace"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
coreV1 "k8s.io/client-go/kubernetes/typed/core/v1"
Expand All @@ -26,11 +28,19 @@ type Config struct {
Name string
Namespace string
K8s coreV1.CoreV1Interface
Kratos kClient.IdentityApi
Kratos kClient.IdentityAPI
}

// TODO @shipperizer worth offloading to a different place as it's going to be reused
type PaginationTokens struct {
First string
Prev string
Next string
}

type IdentitySchemaData struct {
IdentitySchemas []kClient.IdentitySchemaContainer
Tokens PaginationTokens
Error *kClient.GenericError
}

Expand All @@ -48,15 +58,45 @@ type Service struct {
cmNamespace string

k8s coreV1.CoreV1Interface
kratos kClient.IdentityApi
kratos kClient.IdentityAPI

tracer trace.Tracer
monitor monitoring.MonitorInterface
logger logging.LoggerInterface
}

func (s *Service) parseLinkURL(linkURL string) string {
u, err := url.Parse(linkURL)

if err != nil {
s.logger.Errorf("failed to parse link header successfully: %s", err)
return ""
}

return u.Query().Get("page_token")
}

func (s *Service) parsePagination(r *http.Response) PaginationTokens {
links := linkheader.Parse(r.Header.Get("Link"))

pagination := PaginationTokens{}

for _, link := range links {
switch link.Rel {
case "first":
pagination.First = s.parseLinkURL(link.URL)
case "next":
pagination.Next = s.parseLinkURL(link.URL)
case "prev":
pagination.Prev = s.parseLinkURL(link.URL)
}
}

return pagination
}

func (s *Service) parseError(ctx context.Context, r *http.Response) *kClient.GenericError {
ctx, span := s.tracer.Start(ctx, "schemas.Service.parseError")
_, span := s.tracer.Start(ctx, "schemas.Service.parseError")
defer span.End()

gerr := KratosError{Error: kClient.NewGenericErrorWithDefaults()}
Expand All @@ -72,12 +112,12 @@ func (s *Service) parseError(ctx context.Context, r *http.Response) *kClient.Gen
return gerr.Error
}

func (s *Service) ListSchemas(ctx context.Context, page, size int64) (*IdentitySchemaData, error) {
func (s *Service) ListSchemas(ctx context.Context, size int64, token string) (*IdentitySchemaData, error) {
ctx, span := s.tracer.Start(ctx, "schemas.Service.ListSchemas")
defer span.End()

schemas, rr, err := s.kratos.ListIdentitySchemasExecute(
s.kratos.ListIdentitySchemas(ctx).Page(page).PerPage(size),
s.kratos.ListIdentitySchemas(ctx).PageToken(token).PageSize(size),
)

data := new(IdentitySchemaData)
Expand All @@ -87,6 +127,7 @@ func (s *Service) ListSchemas(ctx context.Context, page, size int64) (*IdentityS
data.Error = s.parseError(ctx, rr)
}

data.Tokens = s.parsePagination(rr)
data.IdentitySchemas = schemas

// TODO @shipperizer check if schemas is defaulting to empty slice inside kratos-client
Expand Down
Loading

0 comments on commit 018774c

Please sign in to comment.