Skip to content

Commit

Permalink
Add ConvertSchemaToResourceTypes and ConvertResourceTypesToSchema to …
Browse files Browse the repository at this point in the history
…FGA module
  • Loading branch information
atainter committed Aug 27, 2024
1 parent 55d7825 commit cb4075a
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
*.swp
*.log
*.vscode

.idea/
2 changes: 1 addition & 1 deletion internal/workos/workos.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package workos

const (
// Version represents the SDK version number.
Version = "v4.20.0"
Version = "v4.21.0"
)
96 changes: 96 additions & 0 deletions pkg/fga/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -407,6 +408,34 @@ type QueryResponse struct {
ListMetadata common.ListMetadata `json:"list_metadata"`
}

// Schema
type ConvertSchemaToResourceTypesOpts struct {
// The schema to convert to resource types.
Schema string
}

type ConvertResourceTypesToSchemaOpts struct {
// The version of the transpiler to use.
Version string `json:"version"`

// The resource types to convert to a schema.
ResourceTypes []ResourceType `json:"resource_types"`
}

type ConvertSchemaResponse struct {
// The version transpiler used to convert the schema.
Version string `json:"version"`

// Warnings generated from schema issues.
Warnings []string `json:"warnings,omitempty"`

// The schema generated from the resource types.
Schema *string `json:"schema,omitempty"`

// The resource types generated from the schema.
ResourceTypes []ResourceType `json:"resource_types,omitempty"`
}

// GetResource gets a Resource.
func (c *Client) GetResource(ctx context.Context, opts GetResourceOpts) (Resource, error) {
c.once.Do(c.init)
Expand Down Expand Up @@ -925,3 +954,70 @@ func (c *Client) Query(ctx context.Context, opts QueryOpts) (QueryResponse, erro
err = dec.Decode(&body)
return body, err
}

// ConvertSchemaToResourceTypes converts a schema to resource types.
func (c *Client) ConvertSchemaToResourceTypes(ctx context.Context, opts ConvertSchemaToResourceTypesOpts) (ConvertSchemaResponse, error) {
c.once.Do(c.init)

endpoint := fmt.Sprintf("%s/fga/v1/schemas/convert", c.Endpoint)
req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(opts.Schema))
if err != nil {
return ConvertSchemaResponse{}, err
}

req = req.WithContext(ctx)
req.Header.Set("Content-Type", "text/plain")
req.Header.Set("Authorization", "Bearer "+c.APIKey)
req.Header.Set("User-Agent", "workos-go/"+workos.Version)

res, err := c.HTTPClient.Do(req)
if err != nil {
return ConvertSchemaResponse{}, err
}
defer res.Body.Close()

if err = workos_errors.TryGetHTTPError(res); err != nil {
return ConvertSchemaResponse{}, err
}

var body ConvertSchemaResponse
dec := json.NewDecoder(res.Body)
err = dec.Decode(&body)
return body, err
}

// ConvertResourceTypesToSchema converts resource types to a schema.
func (c *Client) ConvertResourceTypesToSchema(ctx context.Context, opts ConvertResourceTypesToSchemaOpts) (ConvertSchemaResponse, error) {
c.once.Do(c.init)

data, err := c.JSONEncode(opts)
if err != nil {
return ConvertSchemaResponse{}, err
}

endpoint := fmt.Sprintf("%s/fga/v1/schemas/convert", c.Endpoint)
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data))
if err != nil {
return ConvertSchemaResponse{}, err
}

req = req.WithContext(ctx)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.APIKey)
req.Header.Set("User-Agent", "workos-go/"+workos.Version)

res, err := c.HTTPClient.Do(req)
if err != nil {
return ConvertSchemaResponse{}, err
}
defer res.Body.Close()

if err = workos_errors.TryGetHTTPError(res); err != nil {
return ConvertSchemaResponse{}, err
}

var body ConvertSchemaResponse
dec := json.NewDecoder(res.Body)
err = dec.Decode(&body)
return body, err
}
116 changes: 116 additions & 0 deletions pkg/fga/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1316,3 +1316,119 @@ func queryTestHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(body)
}

func convertSchemaToResourceTypesTestHandler(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth != "Bearer test" {
http.Error(w, "bad auth", http.StatusUnauthorized)
return
}

body, err := json.Marshal(ConvertSchemaResponse{
Version: "0.1",
ResourceTypes: []ResourceType{
{
Type: "report",
Relations: map[string]interface{}{
"owner": map[string]interface{}{},
"editor": map[string]interface{}{
"inherit_if": "owner",
},
"viewer": map[string]interface{}{
"inherit_if": "editor",
},
},
},
},
})

if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
w.Write(body)
}

func TestConvertSchemaToResourceTypes(t *testing.T) {
tests := []struct {
scenario string
client *Client
options ConvertSchemaToResourceTypesOpts
expected ConvertSchemaResponse
err bool
}{
{
scenario: "Request without API Key returns an error",
client: &Client{},
err: true,
},
{
scenario: "Request returns ResourceTypes",
client: &Client{
APIKey: "test",
},
options: ConvertSchemaToResourceTypesOpts{
Schema: "version 0.1\n\ntype report\n relation owner []\n relation editor []\n relation viewer []\n \n inherit editor if\n relation owner\n \n inherit viewer if\n relation editor",
},
expected: ConvertSchemaResponse{
Version: "0.1",
ResourceTypes: []ResourceType{
{
Type: "report",
Relations: map[string]interface{}{
"owner": map[string]interface{}{},
"editor": map[string]interface{}{
"inherit_if": "owner",
},
"viewer": map[string]interface{}{
"inherit_if": "editor",
},
},
},
},
},
},
}

for _, test := range tests {
t.Run(test.scenario, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(convertSchemaToResourceTypesTestHandler))
defer server.Close()

client := test.client
client.Endpoint = server.URL
client.HTTPClient = &retryablehttp.HttpClient{Client: *server.Client()}

resourceTypes, err := client.ConvertSchemaToResourceTypes(context.Background(), test.options)
if test.err {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, test.expected, resourceTypes)
})
}
}

func convertResourceTypesToSchemaTestHandler(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth != "Bearer test" {
http.Error(w, "bad auth", http.StatusUnauthorized)
return
}

schema := "version 0.1\n\ntype report\n relation owner []\n relation editor []\n relation viewer []\n \n inherit editor if\n relation owner\n \n inherit viewer if\n relation editor"
body, err := json.Marshal(ConvertSchemaResponse{
Version: "0.1",
Schema: &schema,
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
w.Write(body)
}
14 changes: 14 additions & 0 deletions pkg/fga/fga.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,17 @@ func Query(
) (QueryResponse, error) {
return DefaultClient.Query(ctx, opts)
}

func ConvertSchemaToResourceTypes(
ctx context.Context,
opts ConvertSchemaToResourceTypesOpts,
) (ConvertSchemaResponse, error) {
return DefaultClient.ConvertSchemaToResourceTypes(ctx, opts)
}

func ConvertResourceTypesToSchema(
ctx context.Context,
opts ConvertResourceTypesToSchemaOpts,
) (ConvertSchemaResponse, error) {
return DefaultClient.ConvertResourceTypesToSchema(ctx, opts)
}
71 changes: 71 additions & 0 deletions pkg/fga/fga_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,74 @@ func TestFGAQuery(t *testing.T) {
require.NoError(t, err)
require.Equal(t, expectedResponse, queryResponse)
}

func TestFGAConvertSchemaToResourceTypes(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(convertSchemaToResourceTypesTestHandler))
defer server.Close()

DefaultClient = &Client{
HTTPClient: &retryablehttp.HttpClient{Client: *server.Client()},
Endpoint: server.URL,
}
SetAPIKey("test")

expectedResponse := ConvertSchemaResponse{
Version: "0.1",
ResourceTypes: []ResourceType{
{
Type: "report",
Relations: map[string]interface{}{
"owner": map[string]interface{}{},
"editor": map[string]interface{}{
"inherit_if": "owner",
},
"viewer": map[string]interface{}{
"inherit_if": "editor",
},
},
},
},
}
convertedSchema, err := ConvertSchemaToResourceTypes(context.Background(), ConvertSchemaToResourceTypesOpts{
Schema: "version 0.1\n\ntype report\n relation owner []\n relation editor []\n relation viewer []\n \n inherit editor if\n relation owner\n \n inherit viewer if\n relation editor",
})

require.NoError(t, err)
require.Equal(t, expectedResponse, convertedSchema)
}

func TestFGAConvertResourceTypesToSchema(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(convertResourceTypesToSchemaTestHandler))
defer server.Close()

DefaultClient = &Client{
HTTPClient: &retryablehttp.HttpClient{Client: *server.Client()},
Endpoint: server.URL,
}
SetAPIKey("test")

expectedSchema := "version 0.1\n\ntype report\n relation owner []\n relation editor []\n relation viewer []\n \n inherit editor if\n relation owner\n \n inherit viewer if\n relation editor"
expectedResponse := ConvertSchemaResponse{
Version: "0.1",
Schema: &expectedSchema,
}
convertedSchema, err := ConvertResourceTypesToSchema(context.Background(), ConvertResourceTypesToSchemaOpts{
ResourceTypes: []ResourceType{
{
Type: "report",
Relations: map[string]interface{}{
"owner": map[string]interface{}{},
"editor": map[string]interface{}{
"inherit_if": "owner",
},
"viewer": map[string]interface{}{
"inherit_if": "editor",
},
},
},
},
})

require.NoError(t, err)
require.Equal(t, expectedResponse, convertedSchema)
}

0 comments on commit cb4075a

Please sign in to comment.