-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dev/jgough/10216 support eventsv1 (#41)
* Update to add support for eventv1 events re: AB#10216 --------- Co-authored-by: jgough <[email protected]>
- Loading branch information
1 parent
5fa81b1
commit 9835c1c
Showing
30 changed files
with
1,455 additions
and
1,003 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,15 +16,16 @@ jobs: | |
fetch-depth: 0 | ||
- uses: actions/setup-go@v5 | ||
with: | ||
go-version: '1.22' | ||
go-version: '1.23' | ||
cache: false | ||
|
||
- name: Install Go quality tools | ||
run: | | ||
go install golang.org/x/tools/cmd/[email protected] | ||
go install github.com/axw/gocov/[email protected] | ||
go install github.com/jstemmer/go-junit-report/[email protected] | ||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2 | ||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.60.3 | ||
- name: Install Task | ||
uses: arduino/setup-task@v1 | ||
with: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
package app | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"io" | ||
"strings" | ||
|
||
"github.com/datatrails/go-datatrails-logverification/logverification/app" | ||
) | ||
|
||
/** | ||
* Merklelog related APP contents | ||
* | ||
* An APP in the context of merklelog is an interface that commits log entries. | ||
* | ||
* Apps include: | ||
* - assetsv2 | ||
* - eventsv1 | ||
*/ | ||
|
||
const ( | ||
AssetsV2AppDomain = byte(0) | ||
EventsV1AppDomain = byte(1) | ||
) | ||
|
||
var ( | ||
ErrNoJsonGiven = errors.New("no json given") | ||
) | ||
|
||
// AppDataToVerifiableLogEntries converts the app data (one or more app entries) to verifiable log entries | ||
func AppDataToVerifiableLogEntries(appData []byte, logTenant string) ([]app.AppEntry, error) { | ||
|
||
// first attempt to convert the appdata to a list of events | ||
eventList, err := eventListFromJson(appData) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// now we have an event list we can decipher if the app is | ||
// assetsv2 or eventsv1 | ||
appDomain := appDomain(appData) | ||
|
||
verifiableLogEntries := []app.AppEntry{} | ||
|
||
switch appDomain { | ||
case AssetsV2AppDomain: | ||
// assetsv2 | ||
verfiableAssetsV2Events, err := NewAssetsV2AppEntries(eventList) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
verifiableLogEntries = append(verifiableLogEntries, verfiableAssetsV2Events...) | ||
|
||
case EventsV1AppDomain: | ||
verfiableEventsV1Events, err := NewEventsV1AppEntries(eventList, logTenant) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
verifiableLogEntries = append(verifiableLogEntries, verfiableEventsV1Events...) | ||
|
||
default: | ||
return nil, errors.New("unknown app domain for given app data") | ||
} | ||
|
||
return verifiableLogEntries, nil | ||
} | ||
|
||
// appDomain returns the app domain of the given app data | ||
func appDomain(appData []byte) byte { | ||
|
||
// first attempt to convert the appdata to a list of events | ||
eventList, err := eventListFromJson(appData) | ||
if err != nil { | ||
// if we can't return default of assetsv2 | ||
return AssetsV2AppDomain | ||
} | ||
|
||
// decode into events | ||
events := struct { | ||
Events []json.RawMessage `json:"events,omitempty"` | ||
NextPageToken json.RawMessage `json:"next_page_token,omitempty"` | ||
}{} | ||
|
||
decoder := json.NewDecoder(bytes.NewReader(eventList)) | ||
decoder.DisallowUnknownFields() | ||
for { | ||
err = decoder.Decode(&events) | ||
|
||
if errors.Is(err, io.EOF) { | ||
break | ||
} | ||
|
||
if err != nil { | ||
// return default of assetsv2 | ||
return AssetsV2AppDomain | ||
} | ||
} | ||
|
||
// decode the first event and find the identity | ||
event := struct { | ||
Identity string `json:"identity,omitempty"` | ||
}{} | ||
|
||
decoder = json.NewDecoder(bytes.NewReader(events.Events[0])) | ||
decoder.DisallowUnknownFields() | ||
|
||
for { | ||
err = decoder.Decode(&event) | ||
|
||
if errors.Is(err, io.EOF) { | ||
break | ||
} | ||
|
||
if err != nil { | ||
// if we can't return default of assetsv2 | ||
return AssetsV2AppDomain | ||
} | ||
} | ||
|
||
// find if the event identity is assetsv2 or eventsv1 identity | ||
if strings.HasPrefix(event.Identity, "assets/") { | ||
return AssetsV2AppDomain | ||
} else { | ||
return EventsV1AppDomain | ||
} | ||
|
||
} | ||
|
||
// eventListFromJson normalises a json encoded event or *list* of events, by | ||
// always returning a list of json encoded events. | ||
// | ||
// This converts events from the following apps: | ||
// - assetsv2 | ||
// - eventsv1 | ||
// | ||
// NOTE: there is no json validation done on the event or list of events given | ||
// any valid json will be accepted, use validation logic after this function. | ||
func eventListFromJson(data []byte) ([]byte, error) { | ||
var err error | ||
|
||
doc := struct { | ||
Events []json.RawMessage `json:"events,omitempty"` | ||
NextPageToken json.RawMessage `json:"next_page_token,omitempty"` | ||
}{} | ||
|
||
// check for empty json | ||
// NOTE: also len(nil) == 0, so does the nil check also | ||
if len(data) == 0 { | ||
return nil, ErrNoJsonGiven | ||
} | ||
|
||
decoder := json.NewDecoder(bytes.NewReader(data)) | ||
decoder.DisallowUnknownFields() | ||
|
||
for { | ||
|
||
err = decoder.Decode(&doc) | ||
|
||
// if we can decode the events json | ||
// we know its in the form of a list events json response from | ||
// the list events api, so just return data | ||
if errors.Is(err, io.EOF) { | ||
return data, nil | ||
} | ||
|
||
if err != nil { | ||
break | ||
} | ||
|
||
} | ||
|
||
// if we get here we know that the given data doesn't represent | ||
// a list events json response | ||
// so we can assume its a single event response from the events api. | ||
|
||
var event json.RawMessage | ||
err = json.Unmarshal(data, &event) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// purposefully omit the next page token for response | ||
listEvents := struct { | ||
Events []json.RawMessage `json:"events,omitempty"` | ||
}{} | ||
|
||
listEvents.Events = []json.RawMessage{event} | ||
|
||
events, err := json.Marshal(&listEvents) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return events, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package app | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestEventListFromJson(t *testing.T) { | ||
type args struct { | ||
data []byte | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
expected []byte | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "nil", | ||
args: args{ | ||
data: nil, | ||
}, | ||
expected: nil, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "empty", | ||
args: args{ | ||
data: []byte{}, | ||
}, | ||
expected: nil, | ||
wantErr: true, | ||
}, | ||
// We do need this, since we expect input from other processes via pipes (i.e. an events query) | ||
{ | ||
name: "empty list", | ||
args: args{ | ||
data: []byte(`{"events":[]}`), | ||
}, | ||
expected: []byte(`{"events":[]}`), | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "single event", | ||
args: args{ | ||
data: []byte(`{"identity":"assets/1/events/2"}`), | ||
}, | ||
expected: []byte(`{"events":[{"identity":"assets/1/events/2"}]}`), | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "single list", | ||
args: args{ | ||
data: []byte(`{"events":[{"identity":"assets/1/events/2"}]}`), | ||
}, | ||
expected: []byte(`{"events":[{"identity":"assets/1/events/2"}]}`), | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "multiple list", | ||
args: args{ | ||
data: []byte(`{"events":[{"identity":"assets/1/events/2"},{"identity":"assets/1/events/3"}]}`), | ||
}, | ||
expected: []byte(`{"events":[{"identity":"assets/1/events/2"},{"identity":"assets/1/events/3"}]}`), | ||
wantErr: false, | ||
}, | ||
} | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
actual, err := eventListFromJson(test.args.data) | ||
|
||
assert.Equal(t, test.wantErr, err != nil) | ||
assert.Equal(t, test.expected, actual) | ||
}) | ||
} | ||
} |
Oops, something went wrong.