From 99a1bb48554b4e1a70da7e773178f1865d698259 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Thu, 4 Jan 2024 13:28:27 -0500 Subject: [PATCH 01/16] Preliminary expanders, moved over JSON Path work --- tools/expander.go | 66 ++++++++++++++++++++++++++++++++++++++++++ tools/expander_test.go | 53 +++++++++++++++++++++++++++++++++ tools/jsonpath.go | 55 +++++++++++++++++++++++++++++++++++ tools/jsonpath_test.go | 62 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 tools/expander.go create mode 100644 tools/expander_test.go create mode 100644 tools/jsonpath.go create mode 100644 tools/jsonpath_test.go diff --git a/tools/expander.go b/tools/expander.go new file mode 100644 index 00000000..31c72491 --- /dev/null +++ b/tools/expander.go @@ -0,0 +1,66 @@ +package tools + +import ( + "reflect" + "strings" +) + +type Path struct { + Key string + Value *string +} + +func GetPaths[T any](v *T) []Path { + if v == nil { + return nil + } + + var paths []Path + view := reflect.ValueOf(*v) + typeOfView := view.Type() + + for i := 0; i < view.NumField(); i++ { + field := view.Field(i) + fieldName := typeOfView.Field(i).Name + // Check if the field name ends with "Path" + if strings.HasSuffix(fieldName, "Path") && !field.IsNil() { + pathValue := field.Elem().String() + paths = append(paths, Path{ + Key: fieldName, + Value: &pathValue, + }) + } + } + + return paths +} + +/* Pass in a function to get the inner object that contains the exapnded Paths + * For example: + * In the case of `v AppEntitlementWithUserBindings` you would pass in a getter that returns + * v.AppEntitlementView which is a `*AppEntitlementView`, expects a pointer + */ + +// func GetMappedJSONPaths[T, V any](list []T, getStructWithPaths func(T) *V) (map[string]int, error) { +// if len(list) == 0 { +// return make(map[string]int), nil +// } + +// return MapJSONPaths[T](list, +// } + +func MapJSONPaths[T any](list []T, getPaths func(T) []Path) (map[string]int, error) { + res := make(map[string]int) + for _, item := range list { + for _, path := range getPaths(item) { + index, err := GetJSONPathIndex(path.Value) + if err != nil { + return nil, err + } + if index != -1 { + res[path.Key] = index + } + } + } + return res, nil +} diff --git a/tools/expander_test.go b/tools/expander_test.go new file mode 100644 index 00000000..c4a2b267 --- /dev/null +++ b/tools/expander_test.go @@ -0,0 +1,53 @@ +package tools + +import ( + "testing" + + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" +) + +func strToPtr(str string) *string { + return &str +} + +func TestGetPaths(t *testing.T) { + tests := []struct { + input shared.AppEntitlementView + want []Path + }{ + { + input: shared.AppEntitlementView{ + AppPath: strToPtr("as"), + AppResourcePath: strToPtr("asd"), + AppResourceTypePath: strToPtr("asdads"), + }, + want: []Path{ + Path{ + Key: "AppPath", + Value: strToPtr("as"), + }, + Path{ + Key: "AppResourcePath", + Value: strToPtr("asd"), + }, + Path{ + Key: "AppResourceTypePath", + Value: strToPtr("asdads"), + }, + }, + }, + } + for _, tt := range tests { + t.Run("TestGetPaths", func(t *testing.T) { + got := GetPaths(&tt.input) + for i, gotPath := range got { + if gotPath.Key != tt.want[i].Key { + t.Errorf("GetPaths() Key Error = %v, want %v", gotPath.Key, tt.want[i].Key) + } + if *gotPath.Value != *tt.want[i].Value { + t.Errorf("GetPaths() Value Error = %v, want %v", *gotPath.Value, tt.want[i].Value) + } + } + }) + } +} diff --git a/tools/jsonpath.go b/tools/jsonpath.go new file mode 100644 index 00000000..8449988f --- /dev/null +++ b/tools/jsonpath.go @@ -0,0 +1,55 @@ +package tools + +import ( + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +var jsonIndexRegexExp = regexp.MustCompile(`\[.*\]`) + +func getIndexInsideParentheses(str string) (int, error) { + // Find all matches of the form [.*] then check if there is only one match, to ensure no false positives. + matches := jsonIndexRegexExp.FindAllString(str, 2) + if matches == nil { + return -1, errors.New("jsonpath: invalid path, no array index operation found") + } + + if len(matches) > 1 { + return -1, errors.New("jsonpath: invalid path, multiple array index operations not supported") + } + + match := matches[0][1 : len(matches[0])-1] + if match == "*" { + return -1, errors.New("jsonpath: invalid path, wildcard array indexing not supported") + } + index, err := strconv.Atoi(match) + if err != nil { + return -1, fmt.Errorf("jsonpath: invalid path, only array index operations are supported: %w", err) + } + if index < 0 { + return -1, errors.New("jsonpath: invalid path, negative array indexing not supported") + } + return index, nil +} + +// Implementation of JSONPath only intended to allow expansion of non nested search results using array indexing. +// If you need to support more complex JSONPath operations, please use a library. +// Example: matches "$.expanded[0]". +func GetJSONPathIndex(jsonpath *string) (int, error) { + if jsonpath == nil || *jsonpath == "" { + return -1, nil + } + // Only support dot notation for now. + path := strings.Split(*jsonpath, ".") + if len(path) > 2 { + return -1, errors.New("jsonpath: invalid path, nested jsonpath operations are not supported") + } + + if path[0] != "$" { + return -1, errors.New("jsonpath: invalid path, no root element") + } + return getIndexInsideParentheses(path[1]) +} diff --git a/tools/jsonpath_test.go b/tools/jsonpath_test.go new file mode 100644 index 00000000..c31a4082 --- /dev/null +++ b/tools/jsonpath_test.go @@ -0,0 +1,62 @@ +package tools + +import ( + "testing" +) + +func TestGetJSONPathIndex(t *testing.T) { + tests := []struct { + name string + input *string + want int + wantErr bool + }{ + {name: "Nil", input: nil, want: -1, wantErr: false}, + {name: "Empty", input: newString(""), want: -1, wantErr: false}, + {name: "BadRoot", input: newString("badroot"), want: -1, wantErr: true}, + {name: "NestedPath", input: newString("$.nested.path"), want: -1, wantErr: true}, + {name: "WorkingPath", input: newString("$.expanded[0]"), want: 0, wantErr: false}, + } + + for _, tc := range tests { + got, err := GetJSONPathIndex(tc.input) + if (err != nil) != tc.wantErr { + t.Errorf("%s: expected error: %v, got: %v", tc.name, tc.wantErr, err) + } + if got != tc.want { + t.Errorf("%s: expected: %v, got: %v", tc.name, tc.want, got) + } + } +} + +func TestGetInsideParentheses(t *testing.T) { + tests := []struct { + name string + input string + want int + wantErr bool + }{ + {name: "NoParentheses", input: "no parentheses", want: -1, wantErr: true}, + {name: "NestedParentheses", input: "[[0]]", want: -1, wantErr: true}, + {name: "NumerousParentheses", input: "[1][1]", want: -1, wantErr: true}, + {name: "Wildcard", input: "a[*]", want: -1, wantErr: true}, + {name: "InvalidIndex", input: "aa[invalid]", want: -1, wantErr: true}, + {name: "NegativeIndex", input: "asd[-1]", want: -1, wantErr: true}, + {name: "ProperPath", input: "expanded[1]", want: 1, wantErr: false}, + } + + for _, tc := range tests { + got, err := getIndexInsideParentheses(tc.input) + if (err != nil) != tc.wantErr { + t.Errorf("%s: expected error: %v, got: %v", tc.name, tc.wantErr, err) + } + if got != tc.want { // Adjust this based on the type of 'want' + t.Errorf("%s: expected: %v, got: %v", tc.name, tc.want, got) + } + } +} + +// Helper function to return a pointer to a string. +func newString(s string) *string { + return &s +} From c55ba38eddfbeb98816d886eb52a59506dee9314 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Thu, 4 Jan 2024 13:30:55 -0500 Subject: [PATCH 02/16] Removed uncessary types --- tools/expander_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/expander_test.go b/tools/expander_test.go index c4a2b267..7106436b 100644 --- a/tools/expander_test.go +++ b/tools/expander_test.go @@ -22,15 +22,15 @@ func TestGetPaths(t *testing.T) { AppResourceTypePath: strToPtr("asdads"), }, want: []Path{ - Path{ + { Key: "AppPath", Value: strToPtr("as"), }, - Path{ + { Key: "AppResourcePath", Value: strToPtr("asd"), }, - Path{ + { Key: "AppResourceTypePath", Value: strToPtr("asdads"), }, From 5b3546edf50e25a29abb2f02a0c29cbb19ef47c1 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Thu, 4 Jan 2024 13:58:59 -0500 Subject: [PATCH 03/16] Expanders --- tools/expander.go | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index 31c72491..07da6ea6 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -35,32 +35,30 @@ func GetPaths[T any](v *T) []Path { return paths } -/* Pass in a function to get the inner object that contains the exapnded Paths +/* Pass in the list you want to expand with a function to get the inner object that contains the exapnded Paths * For example: * In the case of `v AppEntitlementWithUserBindings` you would pass in a getter that returns * v.AppEntitlementView which is a `*AppEntitlementView`, expects a pointer */ +func GetMappedJSONPaths[T, V any](item T, getStructWithPaths func(T) *V) (map[string]int, error) { + fn := func(t T) []Path { + v := getStructWithPaths(t) + return GetPaths(v) + } + return mapJSONPaths[T](item, fn) +} -// func GetMappedJSONPaths[T, V any](list []T, getStructWithPaths func(T) *V) (map[string]int, error) { -// if len(list) == 0 { -// return make(map[string]int), nil -// } - -// return MapJSONPaths[T](list, -// } - -func MapJSONPaths[T any](list []T, getPaths func(T) []Path) (map[string]int, error) { +func mapJSONPaths[T any](item T, getPaths func(T) []Path) (map[string]int, error) { res := make(map[string]int) - for _, item := range list { - for _, path := range getPaths(item) { - index, err := GetJSONPathIndex(path.Value) - if err != nil { - return nil, err - } - if index != -1 { - res[path.Key] = index - } + for _, path := range getPaths(item) { + index, err := GetJSONPathIndex(path.Value) + if err != nil { + return nil, err + } + if index != -1 { + res[path.Key] = index } } + return res, nil } From 1bd2dcb3729f5f59b02629f1fcbe0af43e6bbe9f Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Thu, 4 Jan 2024 14:55:53 -0500 Subject: [PATCH 04/16] Marshall expanded types works --- tools/expander.go | 74 ++++++++++++++++++++++++++++++++++++++++++ tools/expander_test.go | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/tools/expander.go b/tools/expander.go index 07da6ea6..fa9b6e70 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -1,8 +1,12 @@ package tools import ( + "encoding/json" + "errors" "reflect" "strings" + + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" ) type Path struct { @@ -62,3 +66,73 @@ func mapJSONPaths[T any](item T, getPaths func(T) []Path) (map[string]int, error return res, nil } + +const ( + atTypeApp = "type.googleapis.com/c1.api.app.v1.App" + atTypeAppResource = "type.googleapis.com/c1.api.app.v1.AppResource" + atTypeAppResourceType = "type.googleapis.com/c1.api.app.v1.AppResourceType" +) + +type marshallable interface { + MarshalJSON() ([]byte, error) +} + +func GetMarshalledObject[T marshallable](input T) (any, error) { + getAtType := func(input *T) *string { + inputVal := reflect.ValueOf(input) + if inputVal.Kind() != reflect.Ptr { + return nil + } + + asTypeMethod := inputVal.MethodByName("GetAtType") + if !asTypeMethod.IsValid() { + return nil + } + + result := asTypeMethod.Call(nil) + if len(result) != 1 { + return nil + } + + return result[0].Interface().(*string) + } + marshall := func(input T) ([]byte, error) { + return input.MarshalJSON() + } + return AtTypeToObject(input, getAtType, marshall) +} + +type marshalJSON[T any] func(T) ([]byte, error) + +func As[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { + d, err := marshal(input) + if err != nil { + return nil, err + } + + var rv V + err = json.Unmarshal(d, &rv) + if err != nil { + return nil, err + } + + return &rv, nil +} + +func AtTypeToObject[T any](input T, getAtType func(*T) *string, marshal marshalJSON[T]) (any, error) { + inputType := getAtType(&input) + if inputType == nil { + return nil, errors.New("input type is nil") + } + + switch *inputType { + case atTypeApp: + return As[T, shared.App](input, marshal) + case atTypeAppResource: + return As[T, shared.AppResource](input, marshal) + case atTypeAppResourceType: + return As[T, shared.AppResourceType](input, marshal) + default: + return nil, errors.New("unknown type") + } +} diff --git a/tools/expander_test.go b/tools/expander_test.go index 7106436b..3cdf3914 100644 --- a/tools/expander_test.go +++ b/tools/expander_test.go @@ -1,7 +1,9 @@ package tools import ( + "encoding/json" "testing" + "time" "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" ) @@ -51,3 +53,57 @@ func TestGetPaths(t *testing.T) { }) } } + +func TestGetMarshalledObject(t *testing.T) { + // Mock data + mockAppID := "123" + now := time.Now() + mockAppResourceType := shared.AppResourceType{ + AppID: &mockAppID, + CreatedAt: &now, + DisplayName: strToPtr("Example App Resource Type"), + ID: strToPtr("456"), + UpdatedAt: &now, + } + + // Marshal to JSON + jsonBytes, err := json.Marshal(mockAppResourceType) + if err != nil { + t.Error("Error marshaling AppResourceType to JSON:", err) + return + } + + // Unmarshal into map[string]interface{} + var result map[string]interface{} + err = json.Unmarshal(jsonBytes, &result) + if err != nil { + t.Error("Error unmarshaling JSON into map[string]interface{}:", err) + return + } + + mockResponse := shared.RequestCatalogSearchServiceSearchEntitlementsResponseExpanded{ + AtType: strToPtr(atTypeAppResourceType), + AdditionalProperties: result, + } + + tests := []struct { + name string + input shared.RequestCatalogSearchServiceSearchEntitlementsResponseExpanded + wantErr bool + }{ + { + name: "Valid AppResourceType", + input: mockResponse, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := GetMarshalledObject(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("GetMarshalledObject() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From 7fafe157bb9a7755b30faf21381627c3ad848ffa Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Thu, 4 Jan 2024 15:23:07 -0500 Subject: [PATCH 05/16] Expander completed, need test cases --- tools/expander.go | 79 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index fa9b6e70..4cf1b279 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -9,6 +9,43 @@ import ( "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" ) +// Populate the expanded map with references to the related objects. +func PopulateExpandedMap(expandMap map[string]int, expanded []any) map[string]*any { + rv := make(map[string]*any) + for k, v := range expandMap { + rv[k] = &expanded[v] + } + return rv +} + +type getStructWithPaths[T, V any] func(T) *V +type makeExpandedObject[T, V any] func(T, map[string]*any) V + +func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList []V, structWithPaths getStructWithPaths[T, K], makeResult makeExpandedObject[T, I]) ([]I, error) { + expanded := make([]any, 0, len(expandedList)) + for _, x := range expandedList { + x := x + converted, err := GetMarshalledObject(x) + if err != nil { + return nil, err + } + expanded = append(expanded, converted) + } + + result := make([]I, 0, len(responseList)) + for _, response := range responseList { + expandedMap, err := GetMappedJSONPaths(response, structWithPaths) + if err != nil { + return nil, err + } + + expandedObjects := PopulateExpandedMap(expandedMap, expanded) + result = append(result, makeResult(response, expandedObjects)) + } + + return result, nil +} + type Path struct { Key string Value *string @@ -44,9 +81,9 @@ func GetPaths[T any](v *T) []Path { * In the case of `v AppEntitlementWithUserBindings` you would pass in a getter that returns * v.AppEntitlementView which is a `*AppEntitlementView`, expects a pointer */ -func GetMappedJSONPaths[T, V any](item T, getStructWithPaths func(T) *V) (map[string]int, error) { +func GetMappedJSONPaths[T, V any](item T, structWithPaths getStructWithPaths[T, V]) (map[string]int, error) { fn := func(t T) []Path { - v := getStructWithPaths(t) + v := structWithPaths(t) return GetPaths(v) } return mapJSONPaths[T](item, fn) @@ -77,25 +114,33 @@ type marshallable interface { MarshalJSON() ([]byte, error) } -func GetMarshalledObject[T marshallable](input T) (any, error) { - getAtType := func(input *T) *string { - inputVal := reflect.ValueOf(input) - if inputVal.Kind() != reflect.Ptr { - return nil - } +func GetAtTypeWithReflection[T any](input *T) *string { + inputVal := reflect.ValueOf(input) + if inputVal.Kind() != reflect.Ptr { + return nil + } - asTypeMethod := inputVal.MethodByName("GetAtType") - if !asTypeMethod.IsValid() { - return nil - } + asTypeMethod := inputVal.MethodByName("GetAtType") + if !asTypeMethod.IsValid() { + return nil + } - result := asTypeMethod.Call(nil) - if len(result) != 1 { - return nil - } + result := asTypeMethod.Call(nil) + if len(result) != 1 { + return nil + } - return result[0].Interface().(*string) + // Check if the result can be converted to *string + asTypeValue, ok := result[0].Interface().(*string) + if !ok { + return nil } + + return asTypeValue +} + +func GetMarshalledObject[T marshallable](input T) (any, error) { + getAtType := GetAtTypeWithReflection[T] marshall := func(input T) ([]byte, error) { return input.MarshalJSON() } From 4ea0fd72aa982fcad955633bbc4d1619ec4cc81f Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Thu, 4 Jan 2024 15:25:50 -0500 Subject: [PATCH 06/16] Hid some methods, based on vibes --- tools/expander.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index 4cf1b279..f239f380 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -10,7 +10,7 @@ import ( ) // Populate the expanded map with references to the related objects. -func PopulateExpandedMap(expandMap map[string]int, expanded []any) map[string]*any { +func populateExpandedMap(expandMap map[string]int, expanded []any) map[string]*any { rv := make(map[string]*any) for k, v := range expandMap { rv[k] = &expanded[v] @@ -39,7 +39,7 @@ func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList return nil, err } - expandedObjects := PopulateExpandedMap(expandedMap, expanded) + expandedObjects := populateExpandedMap(expandedMap, expanded) result = append(result, makeResult(response, expandedObjects)) } @@ -149,7 +149,7 @@ func GetMarshalledObject[T marshallable](input T) (any, error) { type marshalJSON[T any] func(T) ([]byte, error) -func As[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { +func as[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { d, err := marshal(input) if err != nil { return nil, err @@ -172,11 +172,11 @@ func AtTypeToObject[T any](input T, getAtType func(*T) *string, marshal marshalJ switch *inputType { case atTypeApp: - return As[T, shared.App](input, marshal) + return as[T, shared.App](input, marshal) case atTypeAppResource: - return As[T, shared.AppResource](input, marshal) + return as[T, shared.AppResource](input, marshal) case atTypeAppResourceType: - return As[T, shared.AppResourceType](input, marshal) + return as[T, shared.AppResourceType](input, marshal) default: return nil, errors.New("unknown type") } From 3f1c76858f1e537ec992de3b734957742152e6b9 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Thu, 4 Jan 2024 15:29:12 -0500 Subject: [PATCH 07/16] Reorganize stuff --- tools/expander.go | 53 ++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index f239f380..217b4887 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -9,15 +9,21 @@ import ( "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" ) -// Populate the expanded map with references to the related objects. -func populateExpandedMap(expandMap map[string]int, expanded []any) map[string]*any { - rv := make(map[string]*any) - for k, v := range expandMap { - rv[k] = &expanded[v] - } - return rv +const ( + atTypeApp = "type.googleapis.com/c1.api.app.v1.App" + atTypeAppResource = "type.googleapis.com/c1.api.app.v1.AppResource" + atTypeAppResourceType = "type.googleapis.com/c1.api.app.v1.AppResourceType" +) + +type Path struct { + Key string + Value *string +} +type marshallable interface { + MarshalJSON() ([]byte, error) } +type marshalJSON[T any] func(T) ([]byte, error) type getStructWithPaths[T, V any] func(T) *V type makeExpandedObject[T, V any] func(T, map[string]*any) V @@ -39,18 +45,21 @@ func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList return nil, err } - expandedObjects := populateExpandedMap(expandedMap, expanded) + expandedObjects := PopulateExpandedMap(expandedMap, expanded) result = append(result, makeResult(response, expandedObjects)) } return result, nil } -type Path struct { - Key string - Value *string +// Populate the expanded map with references to the related objects. +func PopulateExpandedMap(expandMap map[string]int, expanded []any) map[string]*any { + rv := make(map[string]*any) + for k, v := range expandMap { + rv[k] = &expanded[v] + } + return rv } - func GetPaths[T any](v *T) []Path { if v == nil { return nil @@ -104,16 +113,6 @@ func mapJSONPaths[T any](item T, getPaths func(T) []Path) (map[string]int, error return res, nil } -const ( - atTypeApp = "type.googleapis.com/c1.api.app.v1.App" - atTypeAppResource = "type.googleapis.com/c1.api.app.v1.AppResource" - atTypeAppResourceType = "type.googleapis.com/c1.api.app.v1.AppResourceType" -) - -type marshallable interface { - MarshalJSON() ([]byte, error) -} - func GetAtTypeWithReflection[T any](input *T) *string { inputVal := reflect.ValueOf(input) if inputVal.Kind() != reflect.Ptr { @@ -147,9 +146,7 @@ func GetMarshalledObject[T marshallable](input T) (any, error) { return AtTypeToObject(input, getAtType, marshall) } -type marshalJSON[T any] func(T) ([]byte, error) - -func as[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { +func As[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { d, err := marshal(input) if err != nil { return nil, err @@ -172,11 +169,11 @@ func AtTypeToObject[T any](input T, getAtType func(*T) *string, marshal marshalJ switch *inputType { case atTypeApp: - return as[T, shared.App](input, marshal) + return As[T, shared.App](input, marshal) case atTypeAppResource: - return as[T, shared.AppResource](input, marshal) + return As[T, shared.AppResource](input, marshal) case atTypeAppResourceType: - return as[T, shared.AppResourceType](input, marshal) + return As[T, shared.AppResourceType](input, marshal) default: return nil, errors.New("unknown type") } From 19833d415c8c5b1f0a5a9e6245d8c5108ab8d990 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Fri, 5 Jan 2024 13:29:05 -0500 Subject: [PATCH 08/16] Expanders --- tools/expander.go | 15 ++++--- tools/expander_test.go | 61 ++++++++++++++++++++++++++ tools/test_data/expander_response.json | 1 + 3 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 tools/test_data/expander_response.json diff --git a/tools/expander.go b/tools/expander.go index 217b4887..33a4d1b8 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -25,9 +25,9 @@ type marshallable interface { type marshalJSON[T any] func(T) ([]byte, error) type getStructWithPaths[T, V any] func(T) *V -type makeExpandedObject[T, V any] func(T, map[string]*any) V +type makeExpandedObject[T, V any] func(T, map[string]*any) *V -func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList []V, structWithPaths getStructWithPaths[T, K], makeResult makeExpandedObject[T, I]) ([]I, error) { +func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList []V, structWithPaths getStructWithPaths[T, K], makeResult makeExpandedObject[T, I]) ([]*I, error) { expanded := make([]any, 0, len(expandedList)) for _, x := range expandedList { x := x @@ -37,16 +37,16 @@ func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList } expanded = append(expanded, converted) } - - result := make([]I, 0, len(responseList)) + result := make([]*I, 0, len(responseList)) for _, response := range responseList { + response := response expandedMap, err := GetMappedJSONPaths(response, structWithPaths) if err != nil { return nil, err } - expandedObjects := PopulateExpandedMap(expandedMap, expanded) - result = append(result, makeResult(response, expandedObjects)) + res := makeResult(response, expandedObjects) + result = append(result, res) } return result, nil @@ -93,7 +93,8 @@ func GetPaths[T any](v *T) []Path { func GetMappedJSONPaths[T, V any](item T, structWithPaths getStructWithPaths[T, V]) (map[string]int, error) { fn := func(t T) []Path { v := structWithPaths(t) - return GetPaths(v) + paths := GetPaths(v) + return paths } return mapJSONPaths[T](item, fn) } diff --git a/tools/expander_test.go b/tools/expander_test.go index 3cdf3914..122ccb83 100644 --- a/tools/expander_test.go +++ b/tools/expander_test.go @@ -2,6 +2,8 @@ package tools import ( "encoding/json" + "io" + "os" "testing" "time" @@ -12,6 +14,64 @@ func strToPtr(str string) *string { return &str } +func readJSONFile(filePath string) ([]byte, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + return io.ReadAll(file) +} + +type mockEntitlement struct { + *shared.AppEntitlement + Expanded map[string]*any +} + +func newMockEntitlement(x shared.AppEntitlementView, expanded map[string]*any) *mockEntitlement { + entitlement := x.GetAppEntitlement() + + return &mockEntitlement{ + AppEntitlement: entitlement, + Expanded: expanded, + } +} + +func TestExpandResponse(t *testing.T) { + bytes, err := readJSONFile("test_data/expander_response.json") + if err != nil { + t.Error("Error reading JSON file:", err) + return + } + + var response shared.AppEntitlementSearchServiceSearchResponse + err = json.Unmarshal(bytes, &response) + if err != nil { + t.Error("Error unmarshaling JSON:", err) + return + } + getStructWithPaths := func(response shared.AppEntitlementView) *shared.AppEntitlementView { + return &response + } + + x, err := ExpandResponse(response.List, response.Expanded, getStructWithPaths, newMockEntitlement) + if err != nil { + t.Error("Error expanding response:", err) + return + } + for _, x := range x { + if x.AppEntitlement == nil { + t.Error("AppEntitlement is nil") + return + } + if x.Expanded == nil || len(x.Expanded) == 0 { + t.Error("Expanded is nil or empty") + return + } + } +} + func TestGetPaths(t *testing.T) { tests := []struct { input shared.AppEntitlementView @@ -59,6 +119,7 @@ func TestGetMarshalledObject(t *testing.T) { mockAppID := "123" now := time.Now() mockAppResourceType := shared.AppResourceType{ + // AppID: &mockAppID, CreatedAt: &now, DisplayName: strToPtr("Example App Resource Type"), diff --git a/tools/test_data/expander_response.json b/tools/test_data/expander_response.json new file mode 100644 index 00000000..609271d5 --- /dev/null +++ b/tools/test_data/expander_response.json @@ -0,0 +1 @@ +{"list":[{"appEntitlement":{"appId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "id":"2TisudstXJ9my7s1s1iybjNGtrX", "createdAt":"2023-08-08T23:30:59.460421880Z", "updatedAt":"2023-08-21T18:57:21.108091544Z", "deletedAt":null, "displayName":"2020 Spaces App Access", "description":"Has access to the 2020 Spaces app in Okta", "durationGrant":"43200s", "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "emergencyGrantEnabled":true, "grantCount":"0", "provisionerPolicy":{"connector":{}}, "appResourceTypeId":"2Qo2V98KGEIMDcVmEasgTWgw8hW", "appResourceId":"2TisugQyVc8c3c5xVk6EJIvGsTT", "systemBuiltin":false, "slug":"access", "alias":"ft", "userEditedMask":""}, "appPath":"$.expanded[0]", "appResourceTypePath":"$.expanded[1]", "appResourcePath":"$.expanded[2]"}, {"appEntitlement":{"appId":"2Y8s025sfeULJWA9yaCTWAfVzc2", "id":"2YrhoPFuKMfIh4X5z58spQWnGbX", "createdAt":"2023-11-29T20:29:39.401550089Z", "updatedAt":"2023-12-04T23:56:31.861306245Z", "deletedAt":null, "displayName":"aa: UserGroup member", "description":"Access to aa: userGroup in Bitbucket", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"1", "provisionerPolicy":{"connector":{}}, "appResourceTypeId":"2YJz6Ar45Cq8kbx4CRjVb3dqgcT", "appResourceId":"2YrhoNzORYuMf4KSQnbBb4GF0kS", "systemBuiltin":false, "slug":"member", "alias":"", "userEditedMask":""}, "appPath":"$.expanded[3]", "appResourceTypePath":"$.expanded[4]", "appResourcePath":"$.expanded[5]"}, {"appEntitlement":{"appId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-06-05T22:46:06.722062389Z", "updatedAt":"2023-11-06T22:43:46.528870557Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "emergencyGrantEnabled":true, "grantCount":"5", "provisionerPolicy":{"connector":{}}, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"okta_access", "userEditedMask":""}, "appPath":"$.expanded[0]", "appResourceTypePath":"$.expanded[6]", "appResourcePath":"$.expanded[7]"}, {"appEntitlement":{"appId":"2Qo2VH99Q1qnqog9Vpk0KMrfn3o", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-06-05T22:53:55.263773523Z", "updatedAt":"2023-06-05T22:53:55.763405703Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"1", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[8]", "appResourceTypePath":"$.expanded[9]", "appResourcePath":"$.expanded[10]"}, {"appEntitlement":{"appId":"2Qo2VJ5Fccc8mgfA1DAtLujP4Ky", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-06-05T22:53:55.285135933Z", "updatedAt":"2023-06-05T22:53:55.285141884Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[11]", "appResourceTypePath":"$.expanded[12]", "appResourcePath":"$.expanded[13]"}, {"appEntitlement":{"appId":"2Qo2VKPZCCCkiSr9Af8DH5Jl4oA", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-06-05T22:53:55.306196861Z", "updatedAt":"2023-06-05T22:53:55.306201700Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[14]", "appResourceTypePath":"$.expanded[15]", "appResourcePath":"$.expanded[16]"}, {"appEntitlement":{"appId":"2Qo2VKyGbzEp6DUWoVltnau6clx", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-06-05T22:53:55.328955156Z", "updatedAt":"2023-06-05T22:53:55.572002500Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"1", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[17]", "appResourceTypePath":"$.expanded[18]", "appResourcePath":"$.expanded[19]"}, {"appEntitlement":{"appId":"2Qo36p0ZqiZMeEXY8FMDUwrca2H", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-06-05T22:58:53.571525341Z", "updatedAt":"2023-11-03T23:51:24.671690417Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[20]", "appResourceTypePath":"$.expanded[21]", "appResourcePath":"$.expanded[22]"}, {"appEntitlement":{"appId":"2TisuhnWkxX6TzXnQP7YWs4Hqhj", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-08-08T23:30:59.717130936Z", "updatedAt":"2023-08-08T23:30:59.717134523Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[23]", "appResourceTypePath":"$.expanded[24]", "appResourcePath":"$.expanded[25]"}, {"appEntitlement":{"appId":"2UOvT4aMAP7WetxdoU7gdvtpnG7", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-08-23T20:44:06.226020437Z", "updatedAt":"2023-12-06T01:01:29.625100479Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"62", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[26]", "appResourceTypePath":"$.expanded[27]", "appResourcePath":"$.expanded[28]"}, {"appEntitlement":{"appId":"2Va7nPy3gJ4YJw7kKOyZbSA9xgM", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-09-18T18:41:44.608309354Z", "updatedAt":"2023-09-18T18:48:33.072632585Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"2", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[29]", "appResourceTypePath":"$.expanded[30]", "appResourcePath":"$.expanded[31]"}, {"appEntitlement":{"appId":"2Va8BlQpp7RzEBscOtRO9iwhZ0h", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-09-18T18:44:57.927915854Z", "updatedAt":"2023-09-18T18:44:57.927938777Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[32]", "appResourceTypePath":"$.expanded[33]", "appResourcePath":"$.expanded[34]"}, {"appEntitlement":{"appId":"2VGMcAR9jhzz4J3RifiK7Za7p7b", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-09-11T18:47:21.462117081Z", "updatedAt":"2023-10-04T17:58:40.950032917Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"3", "provisionerPolicy":{"connector":{}}, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":""}, "appPath":"$.expanded[35]", "appResourceTypePath":"$.expanded[36]", "appResourcePath":"$.expanded[37]"}, {"appEntitlement":{"appId":"2XBCJbM3t3F9NVI1q1zWD7dJz8R", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-10-23T20:30:15.522373551Z", "updatedAt":"2023-10-23T21:29:41.750339824Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[38]", "appResourceTypePath":"$.expanded[39]", "appResourcePath":"$.expanded[40]"}, {"appEntitlement":{"appId":"2XBCKLfhzH2sl1oVMpzlzlPQ3x8", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-10-23T20:30:21.749952293Z", "updatedAt":"2023-10-23T20:30:21.749974655Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[41]", "appResourceTypePath":"$.expanded[42]", "appResourcePath":"$.expanded[43]"}, {"appEntitlement":{"appId":"2XDrh4Zmim21ecKV7yEgM8JJmE7", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-10-24T19:10:12.123556315Z", "updatedAt":"2023-10-24T19:28:55.785867328Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"2XDtsEeogS7LLBfrFjEk8gSGIQO", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"1", "provisionerPolicy":{"connector":{}}, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":""}, "appPath":"$.expanded[44]", "appResourceTypePath":"$.expanded[45]", "appResourcePath":"$.expanded[46]"}, {"appEntitlement":{"appId":"2XxDkbY9K8M5oYwlBVhedgRTioV", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:08.673104945Z", "updatedAt":"2023-11-09T20:33:08.673121446Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[47]", "appResourceTypePath":"$.expanded[48]", "appResourcePath":"$.expanded[49]"}, {"appEntitlement":{"appId":"2XxDkO8drppATXkOJW4KLiywS4n", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:07.531865039Z", "updatedAt":"2023-11-09T20:33:07.531885447Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[50]", "appResourceTypePath":"$.expanded[51]", "appResourcePath":"$.expanded[52]"}, {"appEntitlement":{"appId":"2XxDkOEQCi8Dd1uLycnT3So4lvF", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:06.216461298Z", "updatedAt":"2023-11-09T20:33:06.216475335Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[53]", "appResourceTypePath":"$.expanded[54]", "appResourcePath":"$.expanded[55]"}, {"appEntitlement":{"appId":"2XxDkOZQ1cfYmofNNjExoqbcLka", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:06.059147383Z", "updatedAt":"2023-11-09T20:33:06.059165507Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[56]", "appResourceTypePath":"$.expanded[57]", "appResourcePath":"$.expanded[58]"}, {"appEntitlement":{"appId":"2XxDkPg35hv06v5PB7IsykJ5GAg", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:07.026974651Z", "updatedAt":"2023-11-09T20:33:07.026991002Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[59]", "appResourceTypePath":"$.expanded[60]", "appResourcePath":"$.expanded[61]"}, {"appEntitlement":{"appId":"2XxDkQEzCBpf4RHupNCxNX0rwii", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:07.692330878Z", "updatedAt":"2023-11-09T20:33:07.692349443Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[62]", "appResourceTypePath":"$.expanded[63]", "appResourcePath":"$.expanded[64]"}, {"appEntitlement":{"appId":"2XxDkQZLevO2ZjmrOtBUfSOFamp", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:06.859894754Z", "updatedAt":"2023-11-09T20:33:06.859933728Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[65]", "appResourceTypePath":"$.expanded[66]", "appResourcePath":"$.expanded[67]"}, {"appEntitlement":{"appId":"2XxDkRMYm4TF9kNgrFLSp1hW2I3", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:07.852266844Z", "updatedAt":"2023-11-09T20:33:07.852281753Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[68]", "appResourceTypePath":"$.expanded[69]", "appResourcePath":"$.expanded[70]"}, {"appEntitlement":{"appId":"2XxDkRyIw0OXO7bvrcSlgeKBh4l", "id":"287oY0rG4UirjDNFEYguMBvxyim", "createdAt":"2023-11-09T20:33:07.364181728Z", "updatedAt":"2023-11-09T20:33:07.364201506Z", "deletedAt":null, "displayName":"Access", "description":"Local and federated users.", "durationUnset":{}, "riskLevelValueId":"", "complianceFrameworkValueIds":[], "grantPolicyId":"", "revokePolicyId":"", "certifyPolicyId":"", "emergencyGrantPolicyId":"", "emergencyGrantEnabled":false, "grantCount":"0", "provisionerPolicy":null, "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "appResourceId":"287oWNeXnXJl1IqmzTsLknmfw9a", "systemBuiltin":true, "slug":"access", "alias":"", "userEditedMask":null}, "appPath":"$.expanded[71]", "appResourceTypePath":"$.expanded[72]", "appResourcePath":"$.expanded[73]"}], "expanded":[{"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "createdAt":"2023-06-05T22:46:06.171155711Z", "updatedAt":"2023-12-14T22:21:05.932202943Z", "deletedAt":null, "displayName":"Okta", "description":"Integrates with Okta Users, Groups and Roles", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"20n6suDXRPLSiTnRLS8CURTCIq7", "appAccountId":"", "appAccountName":"dev-71620138.okta.com", "iconUrl":"/static/app-icons/okta.svg", "monthlyCostUsd":11, "fieldMask":"monthlyCostUsd", "userCount":"5", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "id":"2Qo2V98KGEIMDcVmEasgTWgw8hW", "createdAt":"2023-06-05T22:53:54.891137440Z", "updatedAt":"2023-06-05T22:53:54.896356990Z", "deletedAt":null, "displayName":"app"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "appResourceTypeId":"2Qo2V98KGEIMDcVmEasgTWgw8hW", "id":"2TisugQyVc8c3c5xVk6EJIvGsTT", "createdAt":"2023-08-08T23:30:59.442326764Z", "updatedAt":"2023-08-08T23:30:59.442405183Z", "deletedAt":null, "displayName":"2020 Spaces", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Y8s025sfeULJWA9yaCTWAfVzc2", "createdAt":"2023-11-13T23:31:54.357057640Z", "updatedAt":"2023-11-30T17:54:41.408683121Z", "deletedAt":null, "displayName":"Bitbucket", "description":"Integrates with Bitbucket workspaces, user groups, users, projects, and repositories.", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"1", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Y8s025sfeULJWA9yaCTWAfVzc2", "id":"2YJz6Ar45Cq8kbx4CRjVb3dqgcT", "createdAt":"2023-11-17T21:58:12.852485153Z", "updatedAt":"2023-11-17T21:58:12.861660887Z", "deletedAt":null, "displayName":"usergroup"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Y8s025sfeULJWA9yaCTWAfVzc2", "appResourceTypeId":"2YJz6Ar45Cq8kbx4CRjVb3dqgcT", "id":"2YrhoNzORYuMf4KSQnbBb4GF0kS", "createdAt":"2023-11-29T20:29:39.288653276Z", "updatedAt":"2023-12-05T00:01:39.008591411Z", "deletedAt":null, "displayName":"aa:", "description":"", "customDescription":"", "grantCount":"1", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-06-05T22:46:06.699922343Z", "updatedAt":"2023-06-05T22:46:06.699997505Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-06-05T22:46:06.705447411Z", "updatedAt":"2023-06-20T19:05:17.769673723Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"5", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Qo2VH99Q1qnqog9Vpk0KMrfn3o", "createdAt":"2023-06-05T22:53:55.121413192Z", "updatedAt":"2023-06-05T22:58:53.477311879Z", "deletedAt":null, "displayName":"ConductorOne", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"https://ok12static.oktacdn.com/fs/bcg/4/gfs89lk1x4v3msI9n5d7", "parentAppId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":"", "userCount":"1", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Qo2VH99Q1qnqog9Vpk0KMrfn3o", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-06-05T22:53:55.257461261Z", "updatedAt":"2023-06-05T22:53:55.257466441Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Qo2VH99Q1qnqog9Vpk0KMrfn3o", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-06-05T22:53:55.260616079Z", "updatedAt":"2023-06-20T19:05:18.239132841Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"1", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Qo2VJ5Fccc8mgfA1DAtLujP4Ky", "createdAt":"2023-06-05T22:53:55.121414896Z", "updatedAt":"2023-06-05T22:58:53.480118819Z", "deletedAt":null, "displayName":"Okta Dashboard", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"https://ok12static.oktacdn.com/assets/img/logos/okta-logo-end-user-dashboard.fc6d8fdbcb8cb4c933d009e71456cec6.svg", "parentAppId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/okta.svg", "monthlyCostUsd":0, "fieldMask":"", "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Qo2VJ5Fccc8mgfA1DAtLujP4Ky", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-06-05T22:53:55.279216917Z", "updatedAt":"2023-06-05T22:53:55.279220544Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Qo2VJ5Fccc8mgfA1DAtLujP4Ky", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-06-05T22:53:55.281967719Z", "updatedAt":"2023-06-05T22:53:55.281971366Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Qo2VKPZCCCkiSr9Af8DH5Jl4oA", "createdAt":"2023-06-05T22:53:55.121416399Z", "updatedAt":"2023-06-05T22:58:53.472480201Z", "deletedAt":null, "displayName":"Okta Browser Plugin", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"https://ok12static.oktacdn.com/assets/img/logos/okta-logo-browser-plugin.1db9f55776407dfc548a5d6985ff280a.svg", "parentAppId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/okta.svg", "monthlyCostUsd":0, "fieldMask":"", "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Qo2VKPZCCCkiSr9Af8DH5Jl4oA", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-06-05T22:53:55.300669889Z", "updatedAt":"2023-06-05T22:53:55.300674167Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Qo2VKPZCCCkiSr9Af8DH5Jl4oA", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-06-05T22:53:55.303323627Z", "updatedAt":"2023-06-05T22:53:55.303330781Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Qo2VKyGbzEp6DUWoVltnau6clx", "createdAt":"2023-06-05T22:53:55.121408323Z", "updatedAt":"2023-06-05T22:58:53.475050432Z", "deletedAt":null, "displayName":"Okta Admin Console", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"https://ok12static.oktacdn.com/assets/img/logos/okta_admin_app.da3325676d57eaf566cb786dd0c7a819.png", "parentAppId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/okta.svg", "monthlyCostUsd":0, "fieldMask":"", "userCount":"1", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Qo2VKyGbzEp6DUWoVltnau6clx", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-06-05T22:53:55.321863118Z", "updatedAt":"2023-06-05T22:53:55.321868538Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Qo2VKyGbzEp6DUWoVltnau6clx", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-06-05T22:53:55.325664140Z", "updatedAt":"2023-06-20T19:05:18.429589186Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"1", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Qo36p0ZqiZMeEXY8FMDUwrca2H", "createdAt":"2023-06-05T22:58:53.385971150Z", "updatedAt":"2023-10-26T20:48:13.221894556Z", "deletedAt":null, "displayName":"Salesforce.com", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"https://ok12static.oktacdn.com/fs/bcg/4/gfsd7z3aoD2cT9yBM5d6", "parentAppId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/salesforce.svg", "monthlyCostUsd":0, "fieldMask":"", "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Qo36p0ZqiZMeEXY8FMDUwrca2H", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-06-05T22:58:53.566071886Z", "updatedAt":"2023-06-05T22:58:53.566075683Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Qo36p0ZqiZMeEXY8FMDUwrca2H", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-06-05T22:58:53.568839501Z", "updatedAt":"2023-10-26T20:48:13.349249848Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2TisuhnWkxX6TzXnQP7YWs4Hqhj", "createdAt":"2023-08-08T23:30:59.626824894Z", "updatedAt":"2023-08-09T00:33:05.423244419Z", "deletedAt":null, "displayName":"2020 Spaces", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"https://ok12static.oktacdn.com/fs/bcg/4/gfs1gzs719qjiVMLW1d8", "parentAppId":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":"", "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2TisuhnWkxX6TzXnQP7YWs4Hqhj", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-08-08T23:30:59.711165721Z", "updatedAt":"2023-08-08T23:30:59.711175409Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2TisuhnWkxX6TzXnQP7YWs4Hqhj", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-08-08T23:30:59.713869038Z", "updatedAt":"2023-08-08T23:30:59.713873537Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2UOvT4aMAP7WetxdoU7gdvtpnG7", "createdAt":"2023-08-23T20:44:05.145353941Z", "updatedAt":"2023-12-06T01:01:28.383562677Z", "deletedAt":null, "displayName":"Confluence", "description":"Integrates with Confluence", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"conductorone.atlassian.net", "iconUrl":"/static/app-icons/confluence.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"62", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2UOvT4aMAP7WetxdoU7gdvtpnG7", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-08-23T20:44:06.200124952Z", "updatedAt":"2023-08-23T20:44:06.200160800Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2UOvT4aMAP7WetxdoU7gdvtpnG7", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-08-23T20:44:06.205974746Z", "updatedAt":"2023-12-06T01:01:28.533148088Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"62", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Va7nPy3gJ4YJw7kKOyZbSA9xgM", "createdAt":"2023-09-18T18:41:43.649989239Z", "updatedAt":"2023-09-18T18:48:32.804969373Z", "deletedAt":null, "displayName":"LDAP", "description":"a", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"2", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Va7nPy3gJ4YJw7kKOyZbSA9xgM", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-09-18T18:41:44.587400788Z", "updatedAt":"2023-09-18T18:41:44.587428551Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Va7nPy3gJ4YJw7kKOyZbSA9xgM", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-09-18T18:41:44.592284230Z", "updatedAt":"2023-09-18T18:48:33.075715220Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"2", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2Va8BlQpp7RzEBscOtRO9iwhZ0h", "createdAt":"2023-09-18T18:44:57.120826180Z", "updatedAt":"2023-09-18T18:44:57.122632806Z", "deletedAt":null, "displayName":"Baton", "description":"Integrates with self-hosted Baton connectors.", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/baton.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2Va8BlQpp7RzEBscOtRO9iwhZ0h", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-09-18T18:44:57.899239103Z", "updatedAt":"2023-09-18T18:44:57.899262588Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2Va8BlQpp7RzEBscOtRO9iwhZ0h", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-09-18T18:44:57.904452770Z", "updatedAt":"2023-09-18T18:44:57.904476886Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2VGMcAR9jhzz4J3RifiK7Za7p7b", "createdAt":"2023-09-11T18:47:20.804171303Z", "updatedAt":"2023-09-14T21:28:49.535650857Z", "deletedAt":null, "displayName":"Expensify", "description":"Integrates with Expensify users, groups, roles, and apps", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/expensify.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"3", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2VGMcAR9jhzz4J3RifiK7Za7p7b", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-09-11T18:47:21.429314906Z", "updatedAt":"2023-09-11T18:47:21.429344192Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2VGMcAR9jhzz4J3RifiK7Za7p7b", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-09-11T18:47:21.437327410Z", "updatedAt":"2023-09-14T21:28:50.564587526Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"3", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XBCJbM3t3F9NVI1q1zWD7dJz8R", "createdAt":"2023-10-23T20:30:14.344409082Z", "updatedAt":"2023-10-23T21:29:41.664031001Z", "deletedAt":null, "displayName":"Population Report Test", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XBCJbM3t3F9NVI1q1zWD7dJz8R", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-10-23T20:30:15.479952259Z", "updatedAt":"2023-10-23T20:30:15.480165102Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XBCJbM3t3F9NVI1q1zWD7dJz8R", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-10-23T20:30:15.492212729Z", "updatedAt":"2023-10-23T21:29:41.804347569Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XBCKLfhzH2sl1oVMpzlzlPQ3x8", "createdAt":"2023-10-23T20:30:20.651023783Z", "updatedAt":"2023-10-23T20:30:20.651043259Z", "deletedAt":null, "displayName":"Population Report Test", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XBCKLfhzH2sl1oVMpzlzlPQ3x8", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-10-23T20:30:21.687463102Z", "updatedAt":"2023-10-23T20:30:21.687478752Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XBCKLfhzH2sl1oVMpzlzlPQ3x8", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-10-23T20:30:21.694664653Z", "updatedAt":"2023-10-23T20:30:21.694679781Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XDrh4Zmim21ecKV7yEgM8JJmE7", "createdAt":"2023-10-24T19:10:06.453898293Z", "updatedAt":"2023-12-14T22:21:05.827702919Z", "deletedAt":null, "displayName":"Twingate", "description":"Integrates with Twingate", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"2XDvAkgmJ3HpwIH7m85ykYwqXCq", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"c11", "iconUrl":"/static/app-icons/twingate.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"1", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XDrh4Zmim21ecKV7yEgM8JJmE7", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-10-24T19:10:12.102421854Z", "updatedAt":"2023-10-24T19:10:12.102444437Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XDrh4Zmim21ecKV7yEgM8JJmE7", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-10-24T19:10:12.108101773Z", "updatedAt":"2023-10-24T19:11:49.575918879Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"Access to the App Access resource, which is a Credential", "grantCount":"1", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkbY9K8M5oYwlBVhedgRTioV", "createdAt":"2023-11-09T20:33:00.487327896Z", "updatedAt":"2023-11-09T20:33:00.487339848Z", "deletedAt":null, "displayName":"Okta Dashboard", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/okta.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkbY9K8M5oYwlBVhedgRTioV", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:08.652499649Z", "updatedAt":"2023-11-09T20:33:08.652516541Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkbY9K8M5oYwlBVhedgRTioV", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:08.657285331Z", "updatedAt":"2023-11-09T20:33:08.657301842Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkO8drppATXkOJW4KLiywS4n", "createdAt":"2023-11-09T20:32:59.815747146Z", "updatedAt":"2023-11-09T20:32:59.815755893Z", "deletedAt":null, "displayName":"Population Report Test", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkO8drppATXkOJW4KLiywS4n", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:07.512233428Z", "updatedAt":"2023-11-09T20:33:07.512251763Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkO8drppATXkOJW4KLiywS4n", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:07.516676320Z", "updatedAt":"2023-11-09T20:33:07.516694605Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkOEQCi8Dd1uLycnT3So4lvF", "createdAt":"2023-11-09T20:32:59.814609299Z", "updatedAt":"2023-11-09T20:32:59.814615230Z", "deletedAt":null, "displayName":"ConductorOne", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkOEQCi8Dd1uLycnT3So4lvF", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:06.197344634Z", "updatedAt":"2023-11-09T20:33:06.197362227Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkOEQCi8Dd1uLycnT3So4lvF", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:06.202063629Z", "updatedAt":"2023-11-09T20:33:06.202081002Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkOZQ1cfYmofNNjExoqbcLka", "createdAt":"2023-11-09T20:32:59.813715425Z", "updatedAt":"2023-12-14T22:21:05.939887293Z", "deletedAt":null, "displayName":"Okta", "description":"Integrates with Okta Users, Groups and Roles", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"20n6suDXRPLSiTnRLS8CURTCIq7", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/okta.svg", "monthlyCostUsd":11, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkOZQ1cfYmofNNjExoqbcLka", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:06.038967792Z", "updatedAt":"2023-11-09T20:33:06.038991006Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkOZQ1cfYmofNNjExoqbcLka", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:06.043546501Z", "updatedAt":"2023-11-09T20:33:06.043571499Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkPg35hv06v5PB7IsykJ5GAg", "createdAt":"2023-11-09T20:32:59.815828871Z", "updatedAt":"2023-11-09T20:32:59.815837908Z", "deletedAt":null, "displayName":"LDAP", "description":"a", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkPg35hv06v5PB7IsykJ5GAg", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:07.006727682Z", "updatedAt":"2023-11-09T20:33:07.006747119Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkPg35hv06v5PB7IsykJ5GAg", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:07.011467156Z", "updatedAt":"2023-11-09T20:33:07.011481734Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkQEzCBpf4RHupNCxNX0rwii", "createdAt":"2023-11-09T20:32:59.814974592Z", "updatedAt":"2023-11-09T20:32:59.814983248Z", "deletedAt":null, "displayName":"Baton", "description":"Integrates with self-hosted Baton connectors.", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/baton.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkQEzCBpf4RHupNCxNX0rwii", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:07.672768630Z", "updatedAt":"2023-11-09T20:33:07.672785101Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkQEzCBpf4RHupNCxNX0rwii", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:07.677042891Z", "updatedAt":"2023-11-09T20:33:07.677060705Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkQZLevO2ZjmrOtBUfSOFamp", "createdAt":"2023-11-09T20:32:59.815390059Z", "updatedAt":"2023-11-09T20:32:59.815396271Z", "deletedAt":null, "displayName":"Confluence", "description":"Integrates with Confluence", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/confluence.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkQZLevO2ZjmrOtBUfSOFamp", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:06.606724013Z", "updatedAt":"2023-11-09T20:33:06.606747187Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkQZLevO2ZjmrOtBUfSOFamp", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:06.612534627Z", "updatedAt":"2023-11-09T20:33:06.612566538Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkRMYm4TF9kNgrFLSp1hW2I3", "createdAt":"2023-11-09T20:32:59.816178764Z", "updatedAt":"2023-11-09T20:32:59.816187561Z", "deletedAt":null, "displayName":"Okta Browser Plugin", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/okta.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkRMYm4TF9kNgrFLSp1hW2I3", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:07.833308469Z", "updatedAt":"2023-11-09T20:33:07.833328167Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkRMYm4TF9kNgrFLSp1hW2I3", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:07.838033085Z", "updatedAt":"2023-11-09T20:33:07.838049185Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}, {"@type":"type.googleapis.com/c1.api.app.v1.App", "id":"2XxDkRyIw0OXO7bvrcSlgeKBh4l", "createdAt":"2023-11-09T20:32:59.815125247Z", "updatedAt":"2023-11-09T20:32:59.815134394Z", "deletedAt":null, "displayName":"Population Report Test", "description":"", "grantPolicyId":"1vPv2JswIqSI9M2DXN0eQhboegU", "revokePolicyId":"1xJnEz4VE1uT1xwHcFfMja0KF6Y", "logoUri":"", "parentAppId":"", "certifyPolicyId":"", "appAccountId":"", "appAccountName":"", "iconUrl":"/static/app-icons/default.svg", "monthlyCostUsd":0, "fieldMask":null, "userCount":"0", "isDirectory":false}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResourceType", "appId":"2XxDkRyIw0OXO7bvrcSlgeKBh4l", "id":"284NHv6xKcUKxFeBzGboCOAxQqz", "createdAt":"2023-11-09T20:33:07.344166271Z", "updatedAt":"2023-11-09T20:33:07.344181189Z", "deletedAt":null, "displayName":"Credential"}, {"@type":"type.googleapis.com/c1.api.app.v1.AppResource", "appId":"2XxDkRyIw0OXO7bvrcSlgeKBh4l", "appResourceTypeId":"284NHv6xKcUKxFeBzGboCOAxQqz", "id":"287oWNeXnXJl1IqmzTsLknmfw9a", "createdAt":"2023-11-09T20:33:07.348104896Z", "updatedAt":"2023-11-09T20:33:07.348119183Z", "deletedAt":null, "displayName":"App Access", "description":"", "customDescription":"", "grantCount":"0", "parentAppResourceTypeId":"", "parentAppResourceId":""}], "nextPageToken":"ChsyUW5qa0RDTU5LU2pNYU9rQlNKbHlodVp2ZFgSZJJwfD51gHfY3l6CUxhE7s1-4VWefpJ2fFpjSI2BMjJIZyCO82_Ocd6kR2llJoqCiIswZ5mq_Ih7pDVhxUSAS2vH4NW2-99tjpEdgtb1h2qrdRF5IN8Lp6R9nLCg1yxrz1GNQmY", "facets":{"facets":[{"displayName":"Resource Type", "iconUrl":"", "param":"resource_type_ids", "value":{"values":[{"displayName":"Group", "iconUrl":"icon:entitlement-resource-type:group", "value":"group", "count":"40"}, {"displayName":"Credential", "iconUrl":"icon:entitlement-resource-type:Credential", "value":"Credential", "count":"32"}, {"displayName":"Org", "iconUrl":"icon:entitlement-resource-type:org", "value":"org", "count":"10"}, {"displayName":"App", "iconUrl":"icon:entitlement-resource-type:app", "value":"app", "count":"7"}, {"displayName":"Project", "iconUrl":"icon:entitlement-resource-type:project", "value":"project", "count":"5"}, {"displayName":"Stadium", "iconUrl":"icon:entitlement-resource-type:stadium", "value":"stadium", "count":"4"}, {"displayName":"Usergroup", "iconUrl":"icon:entitlement-resource-type:usergroup", "value":"usergroup", "count":"4"}, {"displayName":"Repository", "iconUrl":"icon:entitlement-resource-type:repository", "value":"repository", "count":"3"}, {"displayName":"Role", "iconUrl":"icon:entitlement-resource-type:role", "value":"role", "count":"2"}, {"displayName":"Workspace", "iconUrl":"icon:entitlement-resource-type:workspace", "value":"workspace", "count":"1"}]}}, {"displayName":"Applications", "iconUrl":"", "param":"app_ids", "value":{"values":[{"displayName":"Okta", "iconUrl":"icon:appname:okta", "value":"2Qo1YQ24RQMUISlqtpJ4IPzxQdp", "count":"24"}, {"displayName":"Confluence", "iconUrl":"icon:appname:confluence", "value":"2UOvT4aMAP7WetxdoU7gdvtpnG7", "count":"22"}, {"displayName":"Bitbucket", "iconUrl":"icon:appname:bitbucket", "value":"2Y8s025sfeULJWA9yaCTWAfVzc2", "count":"14"}, {"displayName":"LDAP", "iconUrl":"icon:appname:ldap", "value":"2Va7nPy3gJ4YJw7kKOyZbSA9xgM", "count":"13"}, {"displayName":"Expensify", "iconUrl":"icon:appname:expensify", "value":"2VGMcAR9jhzz4J3RifiK7Za7p7b", "count":"5"}, {"displayName":"Twingate", "iconUrl":"icon:appname:twingate", "value":"2XDrh4Zmim21ecKV7yEgM8JJmE7", "count":"4"}, {"displayName":"2020 Spaces", "iconUrl":"icon:appname:2020 spaces", "value":"2TisuhnWkxX6TzXnQP7YWs4Hqhj", "count":"1"}, {"displayName":"Baton", "iconUrl":"icon:appname:baton", "value":"2Va8BlQpp7RzEBscOtRO9iwhZ0h", "count":"1"}, {"displayName":"Population Report Test", "iconUrl":"icon:appname:population report test", "value":"2XBCJbM3t3F9NVI1q1zWD7dJz8R", "count":"1"}, {"displayName":"Population Report Test", "iconUrl":"icon:appname:population report test", "value":"2XBCKLfhzH2sl1oVMpzlzlPQ3x8", "count":"1"}, {"displayName":"Population Report Test", "iconUrl":"icon:appname:population report test", "value":"2XxDkO8drppATXkOJW4KLiywS4n", "count":"1"}, {"displayName":"ConductorOne", "iconUrl":"icon:appname:conductorone", "value":"2XxDkOEQCi8Dd1uLycnT3So4lvF", "count":"1"}, {"displayName":"Okta", "iconUrl":"icon:appname:okta", "value":"2XxDkOZQ1cfYmofNNjExoqbcLka", "count":"1"}, {"displayName":"LDAP", "iconUrl":"icon:appname:ldap", "value":"2XxDkPg35hv06v5PB7IsykJ5GAg", "count":"1"}, {"displayName":"Baton", "iconUrl":"icon:appname:baton", "value":"2XxDkQEzCBpf4RHupNCxNX0rwii", "count":"1"}, {"displayName":"Confluence", "iconUrl":"icon:appname:confluence", "value":"2XxDkQZLevO2ZjmrOtBUfSOFamp", "count":"1"}, {"displayName":"Okta Browser Plugin", "iconUrl":"icon:appname:okta browser plugin", "value":"2XxDkRMYm4TF9kNgrFLSp1hW2I3", "count":"1"}, {"displayName":"Population Report Test", "iconUrl":"icon:appname:population report test", "value":"2XxDkRyIw0OXO7bvrcSlgeKBh4l", "count":"1"}, {"displayName":"Expensify", "iconUrl":"icon:appname:expensify", "value":"2XxDkUiLB7oDUcbOGAi0650vjO3", "count":"1"}, {"displayName":"Salesforce.com", "iconUrl":"icon:appname:salesforce.com", "value":"2XxDkVBjlLkmxWwb9B0LFZQI0Fl", "count":"1"}, {"displayName":"Twingate", "iconUrl":"icon:appname:twingate", "value":"2XxDkWX2ZLuD7jFSPmIURakpbuT", "count":"1"}, {"displayName":"Okta Admin Console", "iconUrl":"icon:appname:okta admin console", "value":"2XxDkWXHABmTqltG51FEWMbAjx5", "count":"1"}, {"displayName":"Okta Dashboard", "iconUrl":"icon:appname:okta dashboard", "value":"2XxDkbY9K8M5oYwlBVhedgRTioV", "count":"1"}, {"displayName":"ConductorOne (Prod)", "iconUrl":"icon:appname:conductorone (prod)", "value":"2YB7ZtNVrvZMPSwKiI4ZN64nkXb", "count":"1"}, {"displayName":"AWS", "iconUrl":"icon:appname:aws", "value":"2YV3fKGyYZYLBDHK5KOODY6AOJ3", "count":"1"}]}}, {"displayName":"Risk level", "iconUrl":"", "param":"risk_level_ids", "value":{"values":[]}}, {"displayName":"Compliance framework", "iconUrl":"", "param":"compliance_framework_ids", "value":{"values":[]}}], "count":"0"}} \ No newline at end of file From 8c0ce28926b893c2776bd0a05da4d1da114277f8 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Fri, 5 Jan 2024 13:29:43 -0500 Subject: [PATCH 09/16] Lint --- tools/expander.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index 33a4d1b8..722e8e5f 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -45,8 +45,7 @@ func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList return nil, err } expandedObjects := PopulateExpandedMap(expandedMap, expanded) - res := makeResult(response, expandedObjects) - result = append(result, res) + result = append(result, makeResult(response, expandedObjects)) } return result, nil From 6c3677ff5ce4f9a08978e9240064a64308eca80a Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Fri, 5 Jan 2024 13:39:59 -0500 Subject: [PATCH 10/16] Added comments --- tools/expander.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tools/expander.go b/tools/expander.go index 722e8e5f..70c72330 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -27,6 +27,9 @@ type marshalJSON[T any] func(T) ([]byte, error) type getStructWithPaths[T, V any] func(T) *V type makeExpandedObject[T, V any] func(T, map[string]*any) *V +/* Pass in the list you want to expand along with the expanded list and a function to get the inner object that contains the expanded Paths and a function to create the result object. + * See expander_test.go `TestExpandResponse` for an example of how to use this function. + */ func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList []V, structWithPaths getStructWithPaths[T, K], makeResult makeExpandedObject[T, I]) ([]*I, error) { expanded := make([]any, 0, len(expandedList)) for _, x := range expandedList { @@ -84,7 +87,7 @@ func GetPaths[T any](v *T) []Path { return paths } -/* Pass in the list you want to expand with a function to get the inner object that contains the exapnded Paths +/* Pass in the list you want to expand with a function to get the inner object that contains the expanded Paths * For example: * In the case of `v AppEntitlementWithUserBindings` you would pass in a getter that returns * v.AppEntitlementView which is a `*AppEntitlementView`, expects a pointer @@ -98,6 +101,7 @@ func GetMappedJSONPaths[T, V any](item T, structWithPaths getStructWithPaths[T, return mapJSONPaths[T](item, fn) } +// Generic function to map the JSON paths to the respective index in the expanded list. func mapJSONPaths[T any](item T, getPaths func(T) []Path) (map[string]int, error) { res := make(map[string]int) for _, path := range getPaths(item) { @@ -113,6 +117,7 @@ func mapJSONPaths[T any](item T, getPaths func(T) []Path) (map[string]int, error return res, nil } +// Use reflection to call the `GetAtType` method on an pointer receiver, func GetAtType() *string. func GetAtTypeWithReflection[T any](input *T) *string { inputVal := reflect.ValueOf(input) if inputVal.Kind() != reflect.Ptr { @@ -138,6 +143,12 @@ func GetAtTypeWithReflection[T any](input *T) *string { return asTypeValue } +/* Generic function using reflection to convert a struct that implements the following interface, note the pointer receiver on GetAtType: + * type Foo struct { + * MarshalJSON() ([]byte, error) + * GetAtType() *string (pointer receiver) + * } + */ func GetMarshalledObject[T marshallable](input T) (any, error) { getAtType := GetAtTypeWithReflection[T] marshall := func(input T) ([]byte, error) { @@ -146,6 +157,7 @@ func GetMarshalledObject[T marshallable](input T) (any, error) { return AtTypeToObject(input, getAtType, marshall) } +// Generic function to marshal an object to JSON then unmarshal it to the desired type. func As[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { d, err := marshal(input) if err != nil { @@ -161,6 +173,7 @@ func As[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { return &rv, nil } +// Convert Speakeasy's `GetAtType` to the respective object. func AtTypeToObject[T any](input T, getAtType func(*T) *string, marshal marshalJSON[T]) (any, error) { inputType := getAtType(&input) if inputType == nil { From 8ff19be35e852cc5f3ac1e8cc234a85cfaf75903 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Fri, 5 Jan 2024 13:42:48 -0500 Subject: [PATCH 11/16] Add convert expanded --- tools/expander.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tools/expander.go b/tools/expander.go index 70c72330..fc85987b 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -54,6 +54,20 @@ func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList return result, nil } +// Convert the expanded response to the desired type. +func ConvertExpanded[T any](e T, key string, getExpanded func(T) map[string]*any) *T { + var rv *T + if x, ok := getExpanded(e)[key]; ok { + if x == nil { + return nil + } + if x, ok := (*x).(*T); ok { + rv = x + } + } + return rv +} + // Populate the expanded map with references to the related objects. func PopulateExpandedMap(expandMap map[string]int, expanded []any) map[string]*any { rv := make(map[string]*any) @@ -173,7 +187,7 @@ func As[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { return &rv, nil } -// Convert Speakeasy's `GetAtType` to the respective object. +// Use Speakeasy's `GetAtType` to convert input to its respective underlying type. func AtTypeToObject[T any](input T, getAtType func(*T) *string, marshal marshalJSON[T]) (any, error) { inputType := getAtType(&input) if inputType == nil { From 33736c9373caf0fc2e381d7c6d7ace5e2ac37440 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Fri, 5 Jan 2024 13:44:00 -0500 Subject: [PATCH 12/16] More work --- tools/expander.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index fc85987b..0f28e1a3 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -30,7 +30,7 @@ type makeExpandedObject[T, V any] func(T, map[string]*any) *V /* Pass in the list you want to expand along with the expanded list and a function to get the inner object that contains the expanded Paths and a function to create the result object. * See expander_test.go `TestExpandResponse` for an example of how to use this function. */ -func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList []V, structWithPaths getStructWithPaths[T, K], makeResult makeExpandedObject[T, I]) ([]*I, error) { +func GetExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList []V, structWithPaths getStructWithPaths[T, K], makeResult makeExpandedObject[T, I]) ([]*I, error) { expanded := make([]any, 0, len(expandedList)) for _, x := range expandedList { x := x @@ -55,7 +55,7 @@ func ExpandResponse[T, K, I any, V marshallable](responseList []T, expandedList } // Convert the expanded response to the desired type. -func ConvertExpanded[T any](e T, key string, getExpanded func(T) map[string]*any) *T { +func GetConvertedExpanded[T any](e T, key string, getExpanded func(T) map[string]*any) *T { var rv *T if x, ok := getExpanded(e)[key]; ok { if x == nil { From 1d1ff402c661e8f5cbc4884089ed82744729da71 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Fri, 5 Jan 2024 13:44:26 -0500 Subject: [PATCH 13/16] Fn rename --- tools/expander_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/expander_test.go b/tools/expander_test.go index 122ccb83..06adca2d 100644 --- a/tools/expander_test.go +++ b/tools/expander_test.go @@ -55,7 +55,7 @@ func TestExpandResponse(t *testing.T) { return &response } - x, err := ExpandResponse(response.List, response.Expanded, getStructWithPaths, newMockEntitlement) + x, err := GetExpandResponse(response.List, response.Expanded, getStructWithPaths, newMockEntitlement) if err != nil { t.Error("Error expanding response:", err) return From 0ce30bcf3997d18270f75af969887f1bacf4f760 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Fri, 5 Jan 2024 15:48:19 -0500 Subject: [PATCH 14/16] Update README and usage files --- .genignore | 3 ++- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++------ USAGE.md | 64 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/.genignore b/.genignore index f63e07a0..fc4bdbc5 100644 --- a/.genignore +++ b/.genignore @@ -9,4 +9,5 @@ uhttp/ .ci go.mod go.sum -USAGE.md \ No newline at end of file +USAGE.md +tools/ \ No newline at end of file diff --git a/README.md b/README.md index 326fb14e..4b83f36d 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ package main import ( "context" - conductoronesdkgo "github.com/conductorone/conductorone-sdk-go/v2" - "github.com/conductorone/conductorone-sdk-go/v2/pkg/models/shared" + conductoronesdkgo "github.com/conductorone/conductorone-sdk-go" + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" "log" ) @@ -256,9 +256,9 @@ package main import ( "context" "errors" - conductoronesdkgo "github.com/conductorone/conductorone-sdk-go/v2" - "github.com/conductorone/conductorone-sdk-go/v2/pkg/models/sdkerrors" - "github.com/conductorone/conductorone-sdk-go/v2/pkg/models/shared" + conductoronesdkgo "github.com/conductorone/conductorone-sdk-go" + "github.com/conductorone/conductorone-sdk-go/pkg/models/sdkerrors" + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" "log" ) @@ -286,7 +286,63 @@ func main() { ``` +## SDK Expander +### Example + +```go +package main + +import ( + "context" + conductoronesdkgo "github.com/conductorone/conductorone-sdk-go" + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" +) + +func main() { + // Create SDK ... + req := shared.RequestCatalogSearchServiceSearchEntitlementsRequest{ + AppEntitlementExpandMask: shared.AppEntitlementExpandMask{ + Paths: []string{"*"}, + }, + } + resp, err := s.sdk.RequestCatalogSearch.SearchEntitlements(ctx, &req) + if err != nil { + return nil, err + } + + response := resp.RequestCatalogSearchServiceSearchEntitlementsResponse + if response == nil { + return nil, nil + } + + getStructWithPaths := func(response shared.AppEntitlementView) *shared.AppEntitlementView { + return &response + } + + expandedResponse, err := GetExpandResponse(response.List, response.Expanded, getStructWithPaths, newMockEntitlement) + if err != nil { + return nil, err + } + return expandedResponse, nil + +} + +type mockEntitlement struct { + *shared.AppEntitlement + Expanded map[string]*any +} + +func newMockEntitlement(x shared.AppEntitlementView, expanded map[string]*any) *mockEntitlement { + entitlement := x.GetAppEntitlement() + + return &mockEntitlement{ + AppEntitlement: entitlement, + Expanded: expanded, + } +} + +``` ## SDK Example Usage with Custom Server/Tenant @@ -297,8 +353,8 @@ package main import ( "context" - conductoronesdkgo "github.com/conductorone/conductorone-sdk-go/v2" - "github.com/conductorone/conductorone-sdk-go/v2/pkg/models/shared" + conductoronesdkgo "github.com/conductorone/conductorone-sdk-go" + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" "log" ) diff --git a/USAGE.md b/USAGE.md index 7706a520..fe91e688 100644 --- a/USAGE.md +++ b/USAGE.md @@ -7,8 +7,8 @@ package main import ( "context" - conductoronesdkgo "github.com/conductorone/conductorone-sdk-go/v2" - "github.com/conductorone/conductorone-sdk-go/v2/pkg/models/shared" + conductoronesdkgo "github.com/conductorone/conductorone-sdk-go" + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" "log" ) @@ -36,7 +36,63 @@ func main() { ``` +## SDK Expander +### Example + +```go +package main + +import ( + "context" + conductoronesdkgo "github.com/conductorone/conductorone-sdk-go" + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" +) + +func main() { + // Create SDK ... + req := shared.RequestCatalogSearchServiceSearchEntitlementsRequest{ + AppEntitlementExpandMask: shared.AppEntitlementExpandMask{ + Paths: []string{"*"}, + }, + } + resp, err := s.sdk.RequestCatalogSearch.SearchEntitlements(ctx, &req) + if err != nil { + return nil, err + } + + response := resp.RequestCatalogSearchServiceSearchEntitlementsResponse + if response == nil { + return nil, nil + } + + getStructWithPaths := func(response shared.AppEntitlementView) *shared.AppEntitlementView { + return &response + } + + expandedResponse, err := GetExpandResponse(response.List, response.Expanded, getStructWithPaths, newMockEntitlement) + if err != nil { + return nil, err + } + return expandedResponse, nil + +} + +type mockEntitlement struct { + *shared.AppEntitlement + Expanded map[string]*any +} + +func newMockEntitlement(x shared.AppEntitlementView, expanded map[string]*any) *mockEntitlement { + entitlement := x.GetAppEntitlement() + + return &mockEntitlement{ + AppEntitlement: entitlement, + Expanded: expanded, + } +} + +``` ## SDK Example Usage with Custom Server/Tenant ### Example @@ -46,8 +102,8 @@ package main import ( "context" - conductoronesdkgo "github.com/conductorone/conductorone-sdk-go/v2" - "github.com/conductorone/conductorone-sdk-go/v2/pkg/models/shared" + conductoronesdkgo "github.com/conductorone/conductorone-sdk-go" + "github.com/conductorone/conductorone-sdk-go/pkg/models/shared" "log" ) From 51cf34b95b6fa9b1b42144d4585711427663a1f0 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Tue, 19 Mar 2024 16:18:37 -0700 Subject: [PATCH 15/16] PR comments --- tools/expander.go | 15 +++++++++------ tools/expander_test.go | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index 0f28e1a3..a37451f3 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -10,9 +10,9 @@ import ( ) const ( - atTypeApp = "type.googleapis.com/c1.api.app.v1.App" - atTypeAppResource = "type.googleapis.com/c1.api.app.v1.AppResource" - atTypeAppResourceType = "type.googleapis.com/c1.api.app.v1.AppResourceType" + AtTypeApp = "type.googleapis.com/c1.api.app.v1.App" + AtTypeAppResource = "type.googleapis.com/c1.api.app.v1.AppResource" + AtTypeAppResourceType = "type.googleapis.com/c1.api.app.v1.AppResourceType" ) type Path struct { @@ -165,6 +165,9 @@ func GetAtTypeWithReflection[T any](input *T) *string { */ func GetMarshalledObject[T marshallable](input T) (any, error) { getAtType := GetAtTypeWithReflection[T] + if getAtType == nil { + return nil, errors.New("input does not implement GetAtType") + } marshall := func(input T) ([]byte, error) { return input.MarshalJSON() } @@ -195,11 +198,11 @@ func AtTypeToObject[T any](input T, getAtType func(*T) *string, marshal marshalJ } switch *inputType { - case atTypeApp: + case AtTypeApp: return As[T, shared.App](input, marshal) - case atTypeAppResource: + case AtTypeAppResource: return As[T, shared.AppResource](input, marshal) - case atTypeAppResourceType: + case AtTypeAppResourceType: return As[T, shared.AppResourceType](input, marshal) default: return nil, errors.New("unknown type") diff --git a/tools/expander_test.go b/tools/expander_test.go index 06adca2d..14372f57 100644 --- a/tools/expander_test.go +++ b/tools/expander_test.go @@ -143,7 +143,7 @@ func TestGetMarshalledObject(t *testing.T) { } mockResponse := shared.RequestCatalogSearchServiceSearchEntitlementsResponseExpanded{ - AtType: strToPtr(atTypeAppResourceType), + AtType: strToPtr(AtTypeAppResourceType), AdditionalProperties: result, } From 71209091022e4974d16930bdd2225a6d9dbcf7b0 Mon Sep 17 00:00:00 2001 From: Anthony Zhao Date: Tue, 19 Mar 2024 16:20:09 -0700 Subject: [PATCH 16/16] fixed lint --- tools/expander.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/expander.go b/tools/expander.go index a37451f3..c4d0f3ec 100644 --- a/tools/expander.go +++ b/tools/expander.go @@ -165,9 +165,6 @@ func GetAtTypeWithReflection[T any](input *T) *string { */ func GetMarshalledObject[T marshallable](input T) (any, error) { getAtType := GetAtTypeWithReflection[T] - if getAtType == nil { - return nil, errors.New("input does not implement GetAtType") - } marshall := func(input T) ([]byte, error) { return input.MarshalJSON() } @@ -194,7 +191,7 @@ func As[T any, V any](input T, marshal marshalJSON[T]) (*V, error) { func AtTypeToObject[T any](input T, getAtType func(*T) *string, marshal marshalJSON[T]) (any, error) { inputType := getAtType(&input) if inputType == nil { - return nil, errors.New("input type is nil") + return nil, errors.New("unable to call getAtType") } switch *inputType {