Skip to content

Commit

Permalink
Refactor filtering implementation (#7)
Browse files Browse the repository at this point in the history
Refactor filtering implementation to resolve #6.

We couldn't continue using `map`, as it does not allow filtering on
multiple values of the same field (i.e., use `OR`) as per
[documentation](https://backstage.io/docs/features/software-catalog/software-catalog-api/#filtering).
  • Loading branch information
tdabasinskas committed Apr 27, 2023
2 parents 10fc17b + aaa199b commit 7f11858
Show file tree
Hide file tree
Showing 9 changed files with 35 additions and 94 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ the low-level details of making HTTP requests and parsing responses, allowing de
With Go installed, run the following to add the package to your project, along with its dependencies:

```bash
go get github.com/tdabasinskas/go-backstage@v1
go get github.com/tdabasinskas/go-backstage/v2
```

Alternatively, you can add import the package as following and run `go get` to install it:

```go
import "github.com/tdabasinskas/go-backstage"
import "github.com/tdabasinskas/go-backstage/v2"
```

## Usage

Add the package to your project as following:

```go
import "github.com/tdabasinskas/go-backstage"
import "github.com/tdabasinskas/go-backstage/v2"
```

Once imported, create a new Backstage API client to access different parts of Backstage API:
Expand All @@ -49,7 +49,7 @@ The client than can be used to access different parts of the API, e.g. get the l

```go
entities, response, err := c.Catalog.Entities.s.List(context.Background(), &ListEntityOptions{
Filters: ListEntityFilter{},
Filters: []string{},
Fields: []string{},
Order: []ListEntityOrder{{ Direction: OrderDescending, Field: "metadata.name" },
},
Expand Down
31 changes: 3 additions & 28 deletions backstage/entity.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package backstage

import (
"bytes"
"context"
"errors"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
)

Expand Down Expand Up @@ -110,9 +108,6 @@ type EntityRelationTarget struct {
Namespace string `json:"namespace"`
}

// ListEntityFilter defines a condition that can be used to filter entities.
type ListEntityFilter map[string]string

// ListEntityOrder defines a condition that can be used to order entities.
type ListEntityOrder struct {
// Direction is the direction to order by.
Expand All @@ -125,7 +120,7 @@ type ListEntityOrder struct {
// ListEntityOptions specifies the optional parameters to the catalogService.List method.
type ListEntityOptions struct {
// Filters is a set of conditions that can be used to filter entities.
Filters ListEntityFilter
Filters []string

// Fields is a set of fields that can be used to limit the response.
Fields []string
Expand Down Expand Up @@ -174,8 +169,8 @@ func (s *entityService) List(ctx context.Context, options *ListEntityOptions) ([

values := u.Query()
if options != nil {
if options.Filters != nil && len(options.Filters) > 0 {
values.Add("filter", options.Filters.string())
for _, f := range options.Filters {
values.Add("filter", f)
}

if options.Fields != nil && len(options.Fields) > 0 {
Expand Down Expand Up @@ -240,26 +235,6 @@ func (s *typedEntityService[T]) get(ctx context.Context, t string, n string, ns
return entity, resp, err
}

// string returns a string representation of the ListEntityFilter.
func (f *ListEntityFilter) string() string {
sortedKeys := make([]string, 0, len(*f))
for key := range *f {
sortedKeys = append(sortedKeys, key)
}
sort.Strings(sortedKeys)

var b bytes.Buffer
for _, k := range sortedKeys {
if (*f)[k] != "" {
b.WriteString(fmt.Sprintf("%s=%s,", k, (*f)[k]))
} else {
b.WriteString(fmt.Sprintf("%s,", k))
}
}

return strings.TrimSuffix(b.String(), ",")
}

// string returns a string representation of the ListEntityOrder.
func (o *ListEntityOrder) string() (string, error) {
if o.Direction != OrderAscending && o.Direction != OrderDescending {
Expand Down
52 changes: 1 addition & 51 deletions backstage/entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,7 @@ func TestEntityServiceList_Filter(t *testing.T) {
s := newEntityService(newCatalogService(c))

actual, resp, err := s.List(context.Background(), &ListEntityOptions{
Filters: ListEntityFilter{
"kind": "User",
},
Filters: []string{"kind=User"},
})
assert.NoError(t, err, "Get should not return an error")
assert.NotEmpty(t, resp, "Response should not be empty")
Expand Down Expand Up @@ -129,7 +127,6 @@ func TestEntityServiceList_Fields(t *testing.T) {
s := newEntityService(newCatalogService(c))

actual, resp, err := s.List(context.Background(), &ListEntityOptions{
Filters: ListEntityFilter{},
Fields: []string{
"metadata.name",
},
Expand Down Expand Up @@ -163,7 +160,6 @@ func TestEntityServiceList_Order(t *testing.T) {
s := newEntityService(newCatalogService(c))

actual, resp, err := s.List(context.Background(), &ListEntityOptions{
Filters: ListEntityFilter{},
Order: []ListEntityOrder{
{
Direction: OrderDescending,
Expand All @@ -182,7 +178,6 @@ func TestEntityServiceList_InvalidOrder(t *testing.T) {
s := newEntityService(newCatalogService(c))

_, _, err := s.List(context.Background(), &ListEntityOptions{
Filters: ListEntityFilter{},
Order: []ListEntityOrder{
{
Direction: "InvalidOrder",
Expand Down Expand Up @@ -213,51 +208,6 @@ func TestEntityServiceDelete(t *testing.T) {
assert.EqualValues(t, http.StatusNoContent, resp.StatusCode, "Response status code should match the one from the server")
}

// TestListEntityFilterString tests if list entity filter string is correctly generated.
func TestListEntityFilterString(t *testing.T) {
tests := []struct {
name string
filter ListEntityFilter
expected string
}{
{
name: "empty filter",
filter: ListEntityFilter{},
expected: "",
},
{
name: "filter with one key-value pair",
filter: ListEntityFilter{
"key1": "value1",
},
expected: "key1=value1",
},
{
name: "filter with one key and no value",
filter: ListEntityFilter{
"key1": "",
},
expected: "key1",
},
{
name: "filter with multiple key-value pairs",
filter: ListEntityFilter{
"key1": "value1",
"key2": "value2",
"key3": "",
},
expected: "key1=value1,key2=value2,key3",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := test.filter.string()
assert.Equal(t, test.expected, actual, "List entity filter string should match expected value")
})
}
}

// TestListEntityOrderString tests if list entity order string is correctly generated.
func TestListEntityOrderString(t *testing.T) {
tests := []struct {
Expand Down
4 changes: 2 additions & 2 deletions examples/entities/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ module exampleentities

go 1.20

replace github.com/tdabasinskas/go-backstage => ../..
replace github.com/tdabasinskas/go-backstage/v2 => ../..

require github.com/tdabasinskas/go-backstage v0.0.0-20230117064629-2a4ac9398d92
require github.com/tdabasinskas/go-backstage/v2 v2.0.0-preview.2
6 changes: 6 additions & 0 deletions examples/entities/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions examples/entities/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package main

import (
"context"
"github.com/tdabasinskas/go-backstage/v2/backstage"
"log"
"os"

"github.com/tdabasinskas/go-backstage/backstage"
)

func main() {
Expand All @@ -26,15 +25,26 @@ func main() {

log.Println("Getting component entities...")
if entities, _, err := c.Catalog.Entities.List(context.Background(), &backstage.ListEntityOptions{
Filters: backstage.ListEntityFilter{
"kind": "component",
},
Filters: []string{"kind=component"},
}); err != nil {
log.Fatal(err)
} else {
log.Printf("Component entities: %v", entities)
}

log.Println("Getting location, API and component entities...")
if entities, _, err := c.Catalog.Entities.List(context.Background(), &backstage.ListEntityOptions{
Filters: []string{
"kind=component",
"kind=location",
"kind=api",
},
}); err != nil {
log.Fatal(err)
} else {
log.Printf("Component, location and API entities: %v", entities)
}

log.Println("Getting specific component entity by UID...")
if entity, _, err := c.Catalog.Entities.Get(context.Background(), "06f15cc8-b6b4-4e44-a9bd-1579029f8fb7"); err != nil {
log.Fatal(err)
Expand Down
4 changes: 2 additions & 2 deletions examples/locations/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ module examplelocations

go 1.20

replace github.com/tdabasinskas/go-backstage => ../..
replace github.com/tdabasinskas/go-backstage/v2 => ../..

require github.com/tdabasinskas/go-backstage v0.0.0-20230117064629-2a4ac9398d92
require github.com/tdabasinskas/go-backstage/v2 v2.0.0-preview.2
2 changes: 1 addition & 1 deletion examples/locations/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"net/http"
"os"

"github.com/tdabasinskas/go-backstage/backstage"
"github.com/tdabasinskas/go-backstage/v2/backstage"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/tdabasinskas/go-backstage
module github.com/tdabasinskas/go-backstage/v2

go 1.20

Expand Down

0 comments on commit 7f11858

Please sign in to comment.