Skip to content

Commit

Permalink
feat: Support exporting tuples in CSV, YAML and JSON format (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhamzeh committed Mar 11, 2024
2 parents 6c254cf + fbcffb9 commit a9dd664
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 22 deletions.
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ linters-settings:
- google.golang.org/protobuf/encoding/protojson
- google.golang.org/protobuf/types/known/structpb
- gopkg.in/yaml.v3
- github.com/gocarina/gocsv
test:
files:
- "$test"
Expand Down
79 changes: 75 additions & 4 deletions cmd/tuple/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ package tuple

import (
"context"
"encoding/json"
"fmt"
"strings"

openfga "github.com/openfga/go-sdk"
"github.com/openfga/go-sdk/client"
Expand All @@ -35,6 +37,55 @@ type readResponse struct {
complete *openfga.ReadResponse
simple []openfga.TupleKey
}
type readResponseCSVDTO struct {
UserType string `csv:"user_type"`
UserID string `csv:"user_id"`
UserRelation string `csv:"user_relation,omitempty"`
Relation string `csv:"relation"`
ObjectType string `csv:"object_type"`
ObjectID string `csv:"object_id"`
ConditionName string `csv:"condition_name,omitempty"`
ConditionContext string `csv:"condition_context,omitempty"`
}

func (r readResponse) toCsvDTO() ([]readResponseCSVDTO, error) {
readResponseDTO := make([]readResponseCSVDTO, 0, len(r.simple))

for _, readRes := range r.simple {
// Handle Condition
conditionName := ""
conditionalContext := ""

if readRes.Condition != nil {
conditionName = readRes.Condition.Name

if readRes.Condition.Context != nil {
b, err := json.Marshal(readRes.Condition.Context)
if err != nil {
return nil, fmt.Errorf("failed to convert condition context to CSV: %w", err)
}

conditionalContext = string(b)
}
}
// Split User and Object
user := strings.Split(readRes.User, ":")
object := strings.Split(readRes.Object, ":")

// Append to DTO
readResponseDTO = append(readResponseDTO, readResponseCSVDTO{
UserType: user[0],
UserID: user[1],
Relation: readRes.Relation,
ObjectType: object[0],
ObjectID: object[1],
ConditionName: conditionName,
ConditionContext: conditionalContext,
})
}

return readResponseDTO, nil
}

func baseRead(fgaClient client.SdkClient, body *client.ClientReadRequest, maxPages int) (
*openfga.ReadResponse, error,
Expand Down Expand Up @@ -126,11 +177,27 @@ var readCmd = &cobra.Command{
}

simpleOutput, _ := cmd.Flags().GetBool("simple-output")
if simpleOutput {
return output.Display(response.simple) //nolint:wrapcheck
outputFormat, _ := cmd.Flags().GetString("output-format")
dataPrinter := output.NewUniPrinter(outputFormat)

if outputFormat == "csv" {
data, _ := response.toCsvDTO()

err := dataPrinter.Display(data)
if err != nil {
return fmt.Errorf("failed to display csv: %w", err)
}

return nil
}
var data any
data = *response.complete

if simpleOutput || outputFormat == "simple-json" {
data = response.simple
}

return output.Display(*response.complete) //nolint:wrapcheck
return dataPrinter.Display(data) //nolint:wrapcheck
},
}

Expand All @@ -139,5 +206,9 @@ func init() {
readCmd.Flags().String("relation", "", "Relation")
readCmd.Flags().String("object", "", "Object")
readCmd.Flags().Int("max-pages", MaxReadPagesLength, "Max number of pages to get. Set to 0 to get all pages.")
readCmd.Flags().Bool("simple-output", false, "Output simpler JSON version. (It can be used by write and delete commands)") //nolint:lll
readCmd.Flags().String("output-format", "json", "Specifies the format for data presentation. Valid options: "+
"json, simple-json, csv, and yaml.")
readCmd.Flags().Bool("simple-output", false, "Output data in simpler version. (It can be used by write and delete commands)") //nolint:lll
_ = readCmd.Flags().MarkDeprecated("simple-output", "the flag \"simple-output\" is deprecated and will be removed"+
" in future releases.\nPlease use the \"--output-format=simple-json\" flag instead.")
}
60 changes: 60 additions & 0 deletions cmd/tuple/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/golang/mock/gomock"
openfga "github.com/openfga/go-sdk"
"github.com/openfga/go-sdk/client"
Expand Down Expand Up @@ -373,3 +375,61 @@ func TestReadMultiPagesMaxLimit(t *testing.T) {
t.Errorf("Expected output %v actual %v", simpleOutput, string(simpleTxt))
}
}

func TestReadResponseCSVDTOParser(t *testing.T) {
t.Parallel()

testCases := []struct {
readRes readResponse
expected []readResponseCSVDTO
}{
{
readRes: readResponse{
simple: []openfga.TupleKey{
{
User: "user:anne",
Relation: "reader",
Object: "document:secret.doc",
Condition: &openfga.RelationshipCondition{
Name: "inOfficeIP",
Context: toPointer(map[string]interface{}{"ip_addr": "10.0.0.1"}),
},
},
{
User: "user:john",
Relation: "writer",
Object: "document:abc.doc",
Condition: &openfga.RelationshipCondition{},
},
},
},
expected: []readResponseCSVDTO{
{
UserType: "user",
UserID: "anne",
Relation: "reader",
ObjectType: "document",
ObjectID: "secret.doc",
ConditionName: "inOfficeIP",
ConditionContext: "{\"ip_addr\":\"10.0.0.1\"}",
},
{
UserType: "user",
UserID: "john",
Relation: "writer",
ObjectType: "document",
ObjectID: "abc.doc",
},
},
},
}

for _, testCase := range testCases {
outcome, _ := testCase.readRes.toCsvDTO()
assert.Equal(t, testCase.expected, outcome)
}
}

func toPointer[T any](p T) *T {
return &p
}
13 changes: 7 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/openfga/cli
go 1.21.8

require (
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/golang/mock v1.6.0
github.com/mattn/go-isatty v0.0.20
github.com/muesli/mango-cobra v1.2.0
Expand All @@ -11,7 +12,7 @@ require (
github.com/oklog/ulid/v2 v2.1.0
github.com/openfga/api/proto v0.0.0-20240201160513-05de9d8be3ee
github.com/openfga/go-sdk v0.3.5
github.com/openfga/language/pkg/go v0.0.0-20240304080135-81c3460c0a04
github.com/openfga/language/pkg/go v0.0.0-20240311093347-a2bba7a149ff
github.com/openfga/openfga v1.5.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
Expand All @@ -33,7 +34,7 @@ require (
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/cel-go v0.20.0 // indirect
github.com/google/cel-go v0.20.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
Expand All @@ -52,8 +53,8 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.49.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/prometheus/common v0.50.0 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
Expand All @@ -76,8 +77,8 @@ require (
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/grpc v1.62.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
26 changes: 14 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
Expand All @@ -67,8 +69,8 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.20.0 h1:h4n6DOCppEMpWERzllyNkntl7JrDyxoE543KWS6BLpc=
github.com/google/cel-go v0.20.0/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down Expand Up @@ -155,8 +157,8 @@ github.com/openfga/api/proto v0.0.0-20240201160513-05de9d8be3ee h1:ZqeLB0dxo4XgR
github.com/openfga/api/proto v0.0.0-20240201160513-05de9d8be3ee/go.mod h1:XF/4W9je/FGBZQ2M5pbQnrzdKF4VcEEtds3ole9sW5E=
github.com/openfga/go-sdk v0.3.5 h1:KQXhMREh+g/K7HNuZ/YmXuHkREkq0VMKteua4bYr3Uw=
github.com/openfga/go-sdk v0.3.5/go.mod h1:u1iErzj5E9/bhe+8nsMv0gigcYbJtImcdgcE5DmpbBg=
github.com/openfga/language/pkg/go v0.0.0-20240304080135-81c3460c0a04 h1:P0ZosAtqnG4o2mP3ymeoL+aTN8n8QXmrTe2pHF4hKuU=
github.com/openfga/language/pkg/go v0.0.0-20240304080135-81c3460c0a04/go.mod h1:XWYBSYB0DEBrKGG/E7FmLA6jC/q/f3bEN+rVNoMXETA=
github.com/openfga/language/pkg/go v0.0.0-20240311093347-a2bba7a149ff h1:SE4oI8sZZkOT4+LTy7SZLtXD6J6IiW7YUma8Lw0DTVE=
github.com/openfga/language/pkg/go v0.0.0-20240311093347-a2bba7a149ff/go.mod h1:uuXSPj7C3ImG5UF3rAupq+aC8mZQ3pbR52GRU/DXjLU=
github.com/openfga/openfga v1.5.0 h1:SfQIN3MnmOx8hCrDKi/uMnKeF4y8gYwUA7lmB/O2Jxk=
github.com/openfga/openfga v1.5.0/go.mod h1:ht3w17zJEx1Zzo+iaAwxF+BtwnZAmUDwpJWw5yxrDQU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
Expand All @@ -176,10 +178,10 @@ github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdU
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI=
github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ=
github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ=
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down Expand Up @@ -329,10 +331,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 h1:8eadJkXbwDEMNwcB5O0s5Y5eCfyuCLdvaiOIaGTrWmQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 h1:9IZDv+/GcI6u+a4jRFRLxQs0RUCfavGfoOgEW6jpkI0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
Expand Down
Loading

0 comments on commit a9dd664

Please sign in to comment.