Skip to content

Commit

Permalink
feat: added support for APIs responding in json array (#1216)
Browse files Browse the repository at this point in the history
* feat: added support for JSON array responses

* feat: added new tests for new json array and negative switch case scenarios

* refactor: added test for empty json object
  • Loading branch information
Yashk767 authored Jul 2, 2024
1 parent e210cf5 commit f619148
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 9 deletions.
22 changes: 22 additions & 0 deletions utils/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,28 @@ func makeAPIRequest(client http.Client, dataSourceURLStruct types.DataSourceURL)
return response, nil
}

func parseJSONData(parsedJSON interface{}, selector string) (interface{}, error) {
switch v := parsedJSON.(type) {
case map[string]interface{}: // Handling JSON object response case
return GetDataFromJSON(v, selector)

case []interface{}: // Handling JSON array of objects response case
if len(v) > 0 {
// The first element from JSON array is fetched
if elem, ok := v[0].(map[string]interface{}); ok {
return GetDataFromJSON(elem, selector)
}
log.Error("Element in array is not a JSON object")
return nil, errors.New("element in array is not a JSON object")
}
log.Error("Empty JSON array")
return nil, errors.New("empty JSON array")
default:
log.Error("Unexpected JSON structure")
return nil, errors.New("unexpected JSON structure")
}
}

func GetDataFromJSON(jsonObject map[string]interface{}, selector string) (interface{}, error) {
if selector[0] == '[' {
selector = "$" + selector
Expand Down
59 changes: 59 additions & 0 deletions utils/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package utils

import (
"encoding/hex"
"encoding/json"
"github.com/stretchr/testify/assert"
"razor/cache"
"razor/core/types"
"reflect"
Expand Down Expand Up @@ -179,6 +181,63 @@ func TestGetDataFromAPI(t *testing.T) {
}
}

func TestParseJSONData(t *testing.T) {
tests := []struct {
name string
input string
selector string
expected interface{}
expectedErr string
}{
{
name: "JSON Object",
input: `{"key1": "value1", "key2": "value2"}`,
selector: "key1",
expected: "value1",
},
{
name: "Array of JSON Objects",
input: `[{"key1": "value1", "key2": "value2"}, {"key1": "value3", "key2": "value4"}]`,
selector: "key2",
expected: "value2",
},
{
name: "Empty JSON Object",
input: `{}`,
selector: "key1",
expectedErr: "unknown key key1",
},
{
name: "Empty JSON Array",
input: `[]`,
selector: "key1",
expectedErr: "empty JSON array",
},
{
name: "Unexpected JSON Structure",
input: `"unexpected structure"`,
selector: "key1",
expectedErr: "unexpected JSON structure",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var parsedJSON interface{}
err := json.Unmarshal([]byte(tt.input), &parsedJSON)
assert.NoError(t, err)

result, err := parseJSONData(parsedJSON, tt.selector)
if tt.expectedErr != "" {
assert.EqualError(t, err, tt.expectedErr)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}

func TestGetDataFromJSON(t *testing.T) {
type args struct {
jsonObject map[string]interface{}
Expand Down
15 changes: 6 additions & 9 deletions utils/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,8 @@ func processJobConcurrently(wg *sync.WaitGroup, mu *sync.Mutex, data *[]*big.Int
}

func (*UtilsStruct) GetDataToCommitFromJob(job bindings.StructsJob, localCache *cache.LocalCache) (*big.Int, error) {
var parsedJSON map[string]interface{}
var (
response []byte
apiErr error
dataSourceURLStruct types.DataSourceURL
)
var dataSourceURLStruct types.DataSourceURL

log.Debugf("Job ID: %d, Getting the data to commit for job %s", job.Id, job.Name)
if isJSONCompatible(job.Url) {
log.Debugf("Job ID: %d, Job URL passed is a struct containing URL along with type of request data", job.Id)
Expand Down Expand Up @@ -322,22 +318,23 @@ func (*UtilsStruct) GetDataToCommitFromJob(job bindings.StructsJob, localCache *
var parsedData interface{}
if job.SelectorType == 0 {
start := time.Now()
response, apiErr = GetDataFromAPI(dataSourceURLStruct, localCache)
response, apiErr := GetDataFromAPI(dataSourceURLStruct, localCache)
if apiErr != nil {
log.Errorf("Job ID: %d, Error in fetching data from API %s: %v", job.Id, job.Url, apiErr)
return nil, apiErr
}
elapsed := time.Since(start).Seconds()
log.Debugf("Job ID: %d, Time taken to fetch the data from API : %s was %f", job.Id, dataSourceURLStruct.URL, elapsed)

var parsedJSON interface{}
err := json.Unmarshal(response, &parsedJSON)
if err != nil {
log.Errorf("Job ID: %d, Error in parsing data from API: %v", job.Id, err)
return nil, err
}
parsedData, err = GetDataFromJSON(parsedJSON, job.Selector)
parsedData, err = parseJSONData(parsedJSON, job.Selector)
if err != nil {
log.Errorf("Job ID: %d, Error in fetching value from parsed data: %v", job.Id, err)
log.Errorf("Job ID: %d, Error in parsing JSON data: %v", job.Id, err)
return nil, err
}
} else {
Expand Down
24 changes: 24 additions & 0 deletions utils/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,16 @@ func TestGetDataToCommitFromJob(t *testing.T) {
Url: `{"type": "POST","url": "https://rpc.ankr.com/eth","body": {"jsonrpc":"2.0","id":7269270904970082,"method":"eth_call","params":[{"from":"0x0000000000000000000000000000000000000000","data":"0xd06ca61f0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000050de6856358cc35f3a9a57eaaa34bd4cb707d2cd0000000000000000000000008e870d67f660d95d5be530380d0ec0bd388289e1","to":"0x7a250d5630b4cf539739df2c5dacb4c659f2488d"},"latest"]},"header": {"content-type": "application/json"}, "returnType": "hexArray[1]"}`,
}

arrayOfObjectsJob := bindings.StructsJob{Id: 1, SelectorType: 0, Weight: 100,
Power: 2, Name: "ethusd_bitfinex", Selector: "last_price",
Url: "https://api.bitfinex.com/v1/pubticker/ethusd",
}

arrayOfArraysJob := bindings.StructsJob{Id: 1, SelectorType: 0, Weight: 100,
Power: 2, Name: "ethusd_bitfinex_v2", Selector: "last_price",
Url: "https://api-pub.bitfinex.com/v2/tickers?symbols=tXDCUSD",
}

invalidDataSourceStructJob := bindings.StructsJob{Id: 1, SelectorType: 0, Weight: 100,
Power: 2, Name: "ethusd_sample", Selector: "result",
Url: `{"type": true,"url1": {}}`,
Expand Down Expand Up @@ -727,6 +737,20 @@ func TestGetDataToCommitFromJob(t *testing.T) {
want: nil,
wantErr: false,
},
{
name: "Test 7: When GetDataToCommitFromJob() executes successfully for job returning response of type array of objects",
args: args{
job: arrayOfObjectsJob,
},
wantErr: false,
},
{
name: "Test 8: When GetDataToCommitFromJob() fails for job returning response of type arrays of arrays as element in array is not a json object",
args: args{
job: arrayOfArraysJob,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down

0 comments on commit f619148

Please sign in to comment.