Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor client to use XML instead of JSON #28

Merged
merged 8 commits into from
Feb 2, 2024
2 changes: 1 addition & 1 deletion r_get_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type RecordAttachment struct {
}

func (c *StoreClient) GetRecord(ctx context.Context, id uuid.UUID) (*Record, error) {
req, err := c.newRequest(ctx)
req, err := c.newRequestXML(ctx)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion r_get_record_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
//
// This returns the number of bytes written and an error if any.
func (c *StoreClient) GetRecordAttachment(ctx context.Context, writer io.Writer, recordID, attachmentID uuid.UUID) (int64, error) {
req, err := c.newRequest(ctx)
req, err := newRequest(ctx, c.c, "")
if err != nil {
return 0, err
}
Expand Down
61 changes: 18 additions & 43 deletions r_get_store_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,35 @@ package easclient

import (
"context"
"time"
"encoding/xml"
)

type StoreStatus struct {
XMLName xml.Name `xml:"status"`
Registry struct {
AllRecords int `json:"allRecords"`
IndexedRecords int `json:"indexedRecords"`
AllAttachments int `json:"allAttachments"`
IndexedAttachments int `json:"indexedAttachments"`
} `json:"registry"`
Records struct {
All int `xml:"all"`
Indexed int `xml:"indexed"`
} `xml:"records"`
Attachments struct {
All int `xml:"all"`
Indexed int `xml:"indexed"`
} `xml:"attachments"`
} `xml:"registry"`
Index struct {
Documents int `json:"documents"`
IsCurrent bool `json:"isCurrent"`
HasDeletions bool `json:"hasDeletions"`
Records int `json:"records"`
Attachments int `json:"attachments"`
} `json:"index"`
Capacity struct {
Maximum int64 `json:"maximum"`
Utilized float64 `json:"utilized"`
GrowthRate float64 `json:"growthRate"`
ExpectedEnd time.Time `json:"expectedEnd"`
Lifetime int `json:"lifetime"`
} `json:"capacity"`
Periods []struct {
Start string `json:"start"`
End string `json:"end"`
Registry struct {
AllRecords int `json:"allRecords"`
IndexedRecords int `json:"indexedRecords"`
AllAttachments int `json:"allAttachments"`
IndexedAttachments int `json:"indexedAttachments"`
} `json:"registry"`
Index struct {
Records int `json:"records"`
Attachments int `json:"attachments"`
} `json:"index"`
Capacity struct {
Utilized float64 `json:"utilized"`
} `json:"capacity"`
} `json:"periods"`
Documents int `xml:"documents"`
IsCurrent bool `xml:"isCurrent"`
HasDeletions bool `xml:"hasDeletions"`
} `xml:"index"`
}

func (c *StoreClient) GetStoreStatus(ctx context.Context) (*StoreStatus, error) {
req, err := c.newRequest(ctx)
req, err := c.newRequestXML(ctx)
if err != nil {
return nil, err
}

type Res struct {
Status *StoreStatus `json:"status"`
}

var result Res
var result StoreStatus

req.SetResult(&result)
res, err := req.Get("/status")
Expand All @@ -67,5 +42,5 @@ func (c *StoreClient) GetStoreStatus(ctx context.Context) (*StoreStatus, error)
return nil, err
}

return result.Status, nil
return &result, nil
}
31 changes: 31 additions & 0 deletions r_get_store_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package easclient_test

import (
"context"
"encoding/xml"
"testing"

"github.com/DEXPRO-Solutions-GmbH/easclient"
Expand All @@ -22,3 +23,33 @@ func TestStoreClient_GetStoreStatus(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, status)
}

func Test_UnmarshalStoreStatus(t *testing.T) {
respBody := `<?xml version="1.0" encoding="UTF-8"?>
<status xmlns="http://namespace.otris.de/2010/09/archive" xmlns:xlink="http://www.w3.org/1999/xlink">
<registry>
<records>
<all>34</all>
<indexed>34</indexed>
</records>
<attachments>
<all>4</all>
<indexed>4</indexed>
</attachments>
</registry>
<index>
<documents>38</documents>
<isCurrent>true</isCurrent>
<hasDeletions>false</hasDeletions>
</index>
</status>`

var resp easclient.StoreStatus

require.NoError(t, xml.Unmarshal([]byte(respBody), &resp))
require.Equal(t, 34, resp.Registry.Records.All)
require.Equal(t, 34, resp.Registry.Records.Indexed)
require.Equal(t, 38, resp.Index.Documents)
require.Equal(t, true, resp.Index.IsCurrent)
require.Equal(t, false, resp.Index.HasDeletions)
}
2 changes: 1 addition & 1 deletion r_put_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type PutStoreRequest struct {
}

func (c *ServerClient) PutStore(ctx context.Context, storeName string, request *PutStoreRequest) error {
req, err := newRequest(ctx, c.c)
req, err := newRequestJSON(ctx, c.c)
if err != nil {
return err
}
Expand Down
17 changes: 8 additions & 9 deletions r_store_post_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,23 @@ package easclient

import (
"context"
"encoding/xml"
"strings"

"github.com/google/uuid"
)

type PostRecordResponse struct {
Records []struct {
Id uuid.UUID `json:"id"`
Link struct {
Type string `json:"type"`
Title string `json:"title"`
Href string `json:"href"`
} `json:"link"`
} `json:"records"`
XMLName xml.Name `xml:"recordArchive"`
ID struct {
Value uuid.UUID `xml:",chardata"`
Type string `xml:"type,attr"`
Href string `xml:"href,attr"`
} `xml:"id"`
}

func (c *StoreClient) PostRecord(ctx context.Context, request *RecordRequest) (*PostRecordResponse, error) {
req, err := c.newRequest(ctx)
req, err := c.newRequestXML(ctx)
if err != nil {
return nil, err
}
Expand Down
5 changes: 1 addition & 4 deletions r_store_post_record_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,5 @@ func TestStoreClient_PostRecord(t *testing.T) {

require.NoError(t, err)
require.NotNil(t, res)

require.Len(t, res.Records, 1)
require.NoError(t, err)
require.NotEqual(t, uuid.Nil, res.Records[0].Id)
require.NotEqual(t, uuid.Nil, res.ID.Value)
}
89 changes: 61 additions & 28 deletions r_store_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package easclient

import (
"context"
"encoding/xml"
"fmt"
"net/url"
"strconv"
"time"

"github.com/google/uuid"
)
Expand Down Expand Up @@ -54,47 +56,78 @@ func SearchRequestFromURL(s string) (*SearchRequest, error) {
}

func (request SearchRequest) ToQuery() map[string]string {
return map[string]string{
q := map[string]string{
"query": request.Query,
"itemsPerPage": strconv.Itoa(request.ItemsPerPage),
"startIndex": strconv.Itoa(request.StartIndex),
"sort": request.Sort,
"sortOrder": request.SortOrder,
}

// delete zero values which would result in an invalid request
if q["itemsPerPage"] == "0" {
delete(q, "itemsPerPage")
}
if q["startIndex"] == "0" {
delete(q, "startIndex")
}

return q
}

type Link struct {
Type string `json:"type"`
Title string `json:"title"`
Href string `json:"href"`
Type string `xml:"type,attr"`
Href string `xml:"href,attr"`
}

type SearchResult struct {
// TODO: Re-add this field once we can confirm this is either always string or bool.
// Title string `json:"title"`
Score float64 `json:"score"`
Id uuid.UUID `json:"id"`
FileLink Link `json:"fileLink"`
ExplainLink Link `json:"explainLink"`
CheckVersionLink Link `json:"checkVersionLink"`
HistoryLink Link `json:"historyLink"`
VerifyLink Link `json:"verifyLink"`
HeaderFields HeaderFields `json:"headerFields"`
RecordFields RecordFields `json:"recordFields"`
type SearchResponse struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
Channel *SearchResponseChannel `xml:"channel"`
}

type SearchResponse struct {
Query string `json:"query"`
TotalHits int `json:"totalHits"`
ItemsPerPage int `json:"itemsPerPage"`
StartIndex int `json:"startIndex"`
Topn int `json:"topn"`
EffectiveResults int `json:"effectiveResults"`
Result []*SearchResult `json:"result"`
type SearchResponseChannel struct {
Title string `xml:"title"`
Link string `xml:"link"`
Description string `xml:"description"`
TotalResults int `xml:"totalResults"` // TODO: Assert in unmarshal test
ItemsPerPage int `xml:"itemsPerPage"` // TODO: Assert in unmarshal test
StartIndex int `xml:"startIndex"` // TODO: Assert in unmarshal test
Query struct {
Role string `xml:"role,attr"`
SearchTerms string `xml:"searchTerms,attr"`
StartPage int `xml:"startPage,attr"`
} `xml:"Query"`
Topn int `xml:"topn"`
EffectiveResults int `xml:"effectiveResults"`
NextPage string `xml:"nextPage"`
Items []*SearchResponseItem `xml:"item"`
}

type SearchResponseItem struct {
Title string `xml:"title"`
Link string `xml:"link"`
Description string `xml:"description"`
Score float64 `xml:"score"`
ExplainLink Link `xml:"explainLink"`
VersionLink Link `xml:"versionLink"`
HistoryLink Link `xml:"historyLink"`
VerifyLink Link `xml:"verifyLink"`
DocumentType string `xml:"documentType"`
Fields []*RecordField `xml:"field"` // TODO: Assert and check in get attachment response if this is the correct way to handle recurring fields
MasterId uuid.UUID `xml:"masterId"`
ArchiveDateTime time.Time `xml:"archiveDateTime"`
ID uuid.UUID `xml:"id"`
Version string `xml:"version"`
ArchiverLogin string `xml:"archiverLogin"`
Archiver string `xml:"archiver"`
InitialArchiver string `xml:"initialArchiver"`
InitialArchiverLogin string `xml:"initialArchiverLogin"`
InitialArchiveDateTime time.Time `xml:"initialArchiveDateTime"`
}

// SearchQuery is similar to Search but expects a URL from which SearchRequest is parsed via SearchRequestFromURL.
func (c *StoreClient) SearchQuery(ctx context.Context, url string) (*SearchResponse, error) {
func (c *StoreClient) SearchQuery(ctx context.Context, url string) (*SearchResponseChannel, error) {
request, err := SearchRequestFromURL(url)
if err != nil {
return nil, fmt.Errorf("failed to parse search request: %w", err)
Expand All @@ -103,8 +136,8 @@ func (c *StoreClient) SearchQuery(ctx context.Context, url string) (*SearchRespo
return c.Search(ctx, request)
}

func (c *StoreClient) Search(ctx context.Context, request *SearchRequest) (*SearchResponse, error) {
req, err := c.newRequest(ctx)
func (c *StoreClient) Search(ctx context.Context, request *SearchRequest) (*SearchResponseChannel, error) {
req, err := c.newRequestXML(ctx)
if err != nil {
return nil, err
}
Expand All @@ -123,5 +156,5 @@ func (c *StoreClient) Search(ctx context.Context, request *SearchRequest) (*Sear
return nil, err
}

return &result, nil
return result.Channel, nil
}
26 changes: 21 additions & 5 deletions r_store_search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,41 @@ func TestStoreClient_Search(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, response)

assert.Equal(t, "Amazo*", response.Query)
assert.Greater(t, response.TotalHits, 0)
// assert search result in general
assert.Equal(t, "Amazo*", response.Query.SearchTerms)
assert.Greater(t, response.TotalResults, 0)
assert.Greater(t, response.EffectiveResults, 0)

// assert single hit
hit := response.Items[0]
require.NotNil(t, hit)
require.Greater(t, len(hit.Fields), 0)
require.Equal(t, "creditor", hit.Fields[0].Name)
require.Equal(t, "Amazon", hit.Fields[0].Value)
})

t.Run("returns results when using pagination details", func(t *testing.T) {
request := &easclient.SearchRequest{
Query: "Amazo*",
ItemsPerPage: 25,
StartIndex: 2500,
StartIndex: 1, // this requires at least 2 records to be present
}

response, err := eastest.DefaultClient().Search(ctx, request)
require.NoError(t, err)
require.NotNil(t, response)

assert.Equal(t, "Amazo*", response.Query)
assert.Greater(t, response.TotalHits, 0)
// assert search result in general
assert.Equal(t, "Amazo*", response.Query.SearchTerms)
assert.Greater(t, response.TotalResults, 0)
assert.Greater(t, response.EffectiveResults, 0)

// assert single hit
hit := response.Items[0]
require.NotNil(t, hit)
require.Greater(t, len(hit.Fields), 0)
require.Equal(t, "creditor", hit.Fields[0].Name)
require.Equal(t, "Amazon", hit.Fields[0].Value)
})
}

Expand Down
Loading
Loading