Skip to content

Commit

Permalink
Support List Spec on HTTP Client
Browse files Browse the repository at this point in the history
Change-Id: Ib7c7f834219b081be1cad48ee80e6409b0df59fa
Closes-bug: #1782665
  • Loading branch information
nati committed Aug 28, 2018
1 parent e3231e2 commit 2724f57
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 24 deletions.
32 changes: 22 additions & 10 deletions pkg/apisrv/client/http.go
Expand Up @@ -10,6 +10,7 @@ import (
"net"
"net/http"
"net/http/httputil"
"net/url"

"github.com/labstack/echo"
"github.com/pkg/errors"
Expand Down Expand Up @@ -138,43 +139,50 @@ func (h *HTTP) Login(ctx context.Context) error {
// Create send a create API request.
func (h *HTTP) Create(ctx context.Context, path string, data interface{}, output interface{}) (*http.Response, error) {
expected := []int{http.StatusOK}
return h.Do(ctx, echo.POST, path, data, output, expected)
return h.Do(ctx, echo.POST, path, nil, data, output, expected)
}

// Read send a get API request.
func (h *HTTP) Read(ctx context.Context, path string, output interface{}) (*http.Response, error) {
expected := []int{http.StatusOK}
return h.Do(ctx, echo.GET, path, nil, output, expected)
return h.Do(ctx, echo.GET, path, nil, nil, output, expected)
}

// ReadWithQuery send a get API request with a query.
func (h *HTTP) ReadWithQuery(
ctx context.Context, path string, query url.Values, output interface{}) (*http.Response, error) {
expected := []int{http.StatusOK}
return h.Do(ctx, echo.GET, path, query, nil, output, expected)
}

// Update send an update API request.
func (h *HTTP) Update(ctx context.Context, path string, data interface{}, output interface{}) (*http.Response, error) {
expected := []int{http.StatusOK}
return h.Do(ctx, echo.PUT, path, data, output, expected)
return h.Do(ctx, echo.PUT, path, nil, data, output, expected)
}

// Delete send a delete API request.
func (h *HTTP) Delete(ctx context.Context, path string, output interface{}) (*http.Response, error) {
expected := []int{http.StatusOK}
return h.Do(ctx, echo.DELETE, path, nil, output, expected)
return h.Do(ctx, echo.DELETE, path, nil, nil, output, expected)
}

// RefUpdate sends a create/update API request/
func (h *HTTP) RefUpdate(ctx context.Context, data interface{}, output interface{}) (*http.Response, error) {
expected := []int{http.StatusOK}
return h.Do(ctx, echo.POST, "/ref-update", data, output, expected)
return h.Do(ctx, echo.POST, "/ref-update", nil, data, output, expected)
}

// EnsureDeleted send a delete API request.
func (h *HTTP) EnsureDeleted(ctx context.Context, path string, output interface{}) (*http.Response, error) {
expected := []int{http.StatusOK, http.StatusNotFound}
return h.Do(ctx, echo.DELETE, path, nil, output, expected)
return h.Do(ctx, echo.DELETE, path, nil, nil, output, expected)
}

// Do issues an API request.
func (h *HTTP) Do(ctx context.Context,
method, path string, data interface{}, output interface{}, expected []int) (*http.Response, error) {
request, err := h.prepareHTTPRequest(method, path, data)
method, path string, query url.Values, data interface{}, output interface{}, expected []int) (*http.Response, error) {
request, err := h.prepareHTTPRequest(method, path, data, query)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -209,7 +217,7 @@ func (h *HTTP) Do(ctx context.Context,
return resp, nil
}

func (h *HTTP) prepareHTTPRequest(method, path string, data interface{}) (*http.Request, error) {
func (h *HTTP) prepareHTTPRequest(method, path string, data interface{}, query url.Values) (*http.Request, error) {
var request *http.Request
if data == nil {
var err error
Expand All @@ -230,6 +238,10 @@ func (h *HTTP) prepareHTTPRequest(method, path string, data interface{}) (*http.
}
}

if len(query) > 0 {
request.URL.RawQuery = query.Encode()
}

request.Header.Set("Content-Type", "application/json")
if h.AuthToken != "" {
request.Header.Set("X-Auth-Token", h.AuthToken)
Expand Down Expand Up @@ -313,7 +325,7 @@ func logErrorAndResponse(err error, response *http.Response) {

// DoRequest requests based on request object.
func (h *HTTP) DoRequest(ctx context.Context, request *Request) (*http.Response, error) {
return h.Do(ctx, request.Method, request.Path, request.Data, &request.Output, request.Expected)
return h.Do(ctx, request.Method, request.Path, nil, request.Data, &request.Output, request.Expected)
}

// Batch execution.
Expand Down
5 changes: 3 additions & 2 deletions pkg/cmd/contrailcli/list.go
Expand Up @@ -136,9 +136,10 @@ func listResources(schemaID string) (string, error) {
Events: []*services.Event{},
}
var response map[string][]interface{}
_, err = client.Read(
_, err = client.ReadWithQuery(
context.Background(),
fmt.Sprintf("%s?%s", pluralPath(schemaID), params.Encode()), &response) //nolint
pluralPath(schemaID),
params, &response)
if err != nil {
fmt.Println(err)
return "", err
Expand Down
12 changes: 6 additions & 6 deletions pkg/db/cassandra/cassandra.go
Expand Up @@ -435,12 +435,12 @@ func NewAmqpEventProcessor() *AmqpEventProcessor {

//AmqpMessage type
type AmqpMessage struct {
RequestID string `json:"request_id"`
Oper string `json:"oper"`
Type string `json:"type"`
UUID string `json:"uuid"`
FqName []string `json:"fq_name"`
Data json.RawMessage `json:"obj_dict"`
RequestID string `json:"request_id"`
Oper string `json:"oper"`
Type string `json:"type"`
UUID string `json:"uuid"`
FqName []string `json:"fq_name"`
Data json.RawMessage `json:"obj_dict"`
}

//Process sends msg to amqp exchange
Expand Down
69 changes: 65 additions & 4 deletions pkg/services/baseservices/api.go
@@ -1,6 +1,7 @@
package baseservices

import (
"net/url"
"strconv"
"strings"

Expand Down Expand Up @@ -47,7 +48,7 @@ func parseStringList(query string) []string {
return strings.Split(query, ",")
}

//GetListSpec makes ListSpec from Query Parameters
// GetListSpec makes ListSpec from Query Parameters
func GetListSpec(c echo.Context) *ListSpec {
filters := ParseFilter(c.QueryParam(FiltersKey))
pageMarker := parsePositiveNumber(c.QueryParam(PageMarkerKey), 0)
Expand Down Expand Up @@ -79,7 +80,45 @@ func GetListSpec(c echo.Context) *ListSpec {
}
}

//AppendFilter return a filter for specific key.
// URLQuery returns URL query strings.
func (s *ListSpec) URLQuery() url.Values {
if s == nil {
return nil
}
query := url.Values{}
addQuery(query, FiltersKey, EncodeFilter(s.Filters))
if s.Offset > 0 {
addQuery(query, PageMarkerKey, strconv.FormatInt(s.Offset, 10))
}
if s.Limit > 0 {
addQuery(query, PageLimitKey, strconv.FormatInt(s.Limit, 10))
}
addQueryBool(query, DetailKey, s.Detail)
addQueryBool(query, CountKey, s.Count)
addQueryBool(query, SharedKey, s.Shared)
addQueryBool(query, ExcludeHRefsKey, s.ExcludeHrefs)
addQuery(query, ParentTypeKey, s.ParentType)
addQuery(query, ParentFQNameKey, basemodels.FQNameToString(s.ParentFQName))
addQuery(query, ParentUUIDsKey, encodeStringList(s.ParentUUIDs))
addQuery(query, BackrefUUIDsKey, encodeStringList(s.BackRefUUIDs))
addQuery(query, ObjectUUIDsKey, encodeStringList(s.ObjectUUIDs))
addQuery(query, FieldsKey, encodeStringList(s.Fields))
return query
}

func addQuery(query url.Values, key, value string) {
if value != "" {
query.Add(key, value)
}
}

func addQueryBool(query url.Values, key string, value bool) {
if value {
query.Add(key, strconv.FormatBool(value))
}
}

// AppendFilter return a filter for specific key.
func AppendFilter(filters []*Filter, key string, values ...string) []*Filter {
var filter *Filter
if len(values) == 0 {
Expand All @@ -102,8 +141,17 @@ func AppendFilter(filters []*Filter, key string, values ...string) []*Filter {
return filters
}

//ParseFilter makes Filter from comma separated string.
//Eg. check==a,check==b,name==Bob
// QueryString returns string for query string.
func (f *Filter) QueryString() string {
var sl []string
for _, value := range f.Values {
sl = append(sl, f.Key+"=="+value)
}
return encodeStringList(sl)
}

// ParseFilter makes Filter from comma separated string.
// Eg. check==a,check==b,name==Bob
func ParseFilter(filterString string) []*Filter {
filters := []*Filter{}
if filterString == "" {
Expand All @@ -121,3 +169,16 @@ func ParseFilter(filterString string) []*Filter {
}
return filters
}

// EncodeFilter encodes filter to string.
func EncodeFilter(filters []*Filter) string {
var sl []string
for _, filter := range filters {
sl = append(sl, filter.QueryString())
}
return encodeStringList(sl)
}

func encodeStringList(s []string) string {
return strings.Join(s, ",")
}
62 changes: 62 additions & 0 deletions pkg/services/baseservices/api_test.go
Expand Up @@ -19,3 +19,65 @@ func TestParseFilter(t *testing.T) {
},
}, filter, "parse filter correctly")
}

func TestEncodeFilter(t *testing.T) {
filterString := EncodeFilter([]*Filter{
{
Key: "check",
Values: []string{"a", "b"},
},
{
Key: "name",
Values: []string{"Bob"},
},
})
assert.Equal(t, "check==a,check==b,name==Bob", filterString)
}

func TestEncodeNilFilter(t *testing.T) {
filterString := EncodeFilter(nil)
assert.Equal(t, "", filterString)
}

func TestEmptyURLQuerySpec(t *testing.T) {
spec := &ListSpec{}
assert.Equal(t, "", spec.URLQuery().Encode())
}

func TestNilURLQuerySpec(t *testing.T) {
var spec *ListSpec
assert.Equal(t, "", spec.URLQuery().Encode())
}

func TestURLQuerySpec(t *testing.T) {
spec := &ListSpec{
Limit: 10,
Offset: 10,
Detail: true,
Filters: []*Filter{
{
Key: "check",
Values: []string{"a", "b"},
},
{
Key: "name",
Values: []string{"Bob"},
},
},
Fields: []string{"a", "b", "c"},
ParentType: "test",
ParentFQName: []string{"a", "b"},
Count: true,
ExcludeHrefs: true,
Shared: true,
ParentUUIDs: []string{"a", "b"},
BackRefUUIDs: []string{"a", "b"},
ObjectUUIDs: []string{"a", "b"},
}
assert.Equal(t,
"back_ref_id=a%2Cb&count=true&detail=true&exclude_hrefs=true"+
"&fields=a%2Cb%2Cc&filters=check%3D%3Da%2Ccheck%3D%3Db%2Cname%3D%3DBob"+
"&obj_uuids=a%2Cb&pageLimit=10&pageMarker=10"+
"&parent_fq_name_str=a%3Ab&parent_id=a%2Cb&parent_type=test&shared=true",
spec.URLQuery().Encode())
}
4 changes: 3 additions & 1 deletion pkg/testutil/integration/api_server_client.go
Expand Up @@ -94,6 +94,7 @@ func (c *HTTPAPIClient) FQNameToID(t *testing.T, fqName []string, resourceType s
context.Background(),
echo.POST,
fqNameToIDPath,
nil,
&fqNameToIDLegacyRequest{
FQName: fqName,
Type: resourceType,
Expand All @@ -118,6 +119,7 @@ func (c *HTTPAPIClient) Chown(t *testing.T, owner, uuid string) {
context.Background(),
echo.POST,
chownPath,
nil,
&chownRequest{
Owner: owner,
UUID: uuid,
Expand Down Expand Up @@ -224,6 +226,6 @@ func (c *HTTPAPIClient) DeleteResource(t *testing.T, path string) {
// CheckResourceDoesNotExist checks that there is no resource with given path.
func (c *HTTPAPIClient) CheckResourceDoesNotExist(t *testing.T, path string) {
var responseData interface{}
r, err := c.Do(context.Background(), echo.GET, path, nil, &responseData, []int{http.StatusNotFound})
r, err := c.Do(context.Background(), echo.GET, path, nil, nil, &responseData, []int{http.StatusNotFound})
assert.NoError(t, err, "getting resource failed\n response: %+v\n responseData: %+v", r, responseData)
}
2 changes: 1 addition & 1 deletion tools/templates/client.tmpl
Expand Up @@ -36,7 +36,7 @@ func (h *HTTP) Get{{ schema.JSONSchema.GoName }}(ctx context.Context, request *s
func (h *HTTP) List{{ schema.JSONSchema.GoName }}(ctx context.Context, request *services.List{{ schema.JSONSchema.GoName }}Request) (*services.List{{ schema.JSONSchema.GoName }}Response, error) {
//TODO(nati) support encoding list spec for query param.
response := &services.List{{ schema.JSONSchema.GoName }}Response{}
_, err := h.Read(ctx, "{{ schema.Prefix }}{{ schema.PluralPath }}", response)
_, err := h.ReadWithQuery(ctx, "{{ schema.Prefix }}{{ schema.PluralPath }}", request.GetSpec().URLQuery(), response)
return response, err
}

Expand Down

0 comments on commit 2724f57

Please sign in to comment.