Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 13172de

Browse files
Add json-diff
1 parent 3ce23ea commit 13172de

File tree

6 files changed

+273
-0
lines changed

6 files changed

+273
-0
lines changed

‎json-diff/api.go‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"net/http"
7+
"strings"
8+
)
9+
10+
//callApi - make a http request to the end points
11+
//It need not be http request, it can be any external api call (grpc, graphql, etc.)
12+
func callAPI(endpoint string, params *RequestParams) (string, error) {
13+
client := http.Client{}
14+
15+
url := endpoint
16+
17+
//Add query params
18+
qparams := make([]string, 0)
19+
for k, v := range params.QueryParams {
20+
qparams = append(qparams, k+"="+v)
21+
}
22+
if len(qparams) > 0 {
23+
url += "?" + strings.Join(qparams, "&")
24+
}
25+
26+
req, err := http.NewRequest("GET", url, nil)
27+
if err != nil {
28+
return "", fmt.Errorf("failed to create request %v", err)
29+
}
30+
31+
for k, v := range params.Headers {
32+
req.Header.Add(k, v)
33+
}
34+
35+
resp, err := client.Do(req)
36+
if err != nil {
37+
return "", fmt.Errorf("failed to request api %v", err)
38+
}
39+
defer resp.Body.Close()
40+
41+
b, err := ioutil.ReadAll(resp.Body)
42+
return string(b), nil
43+
}

‎json-diff/consumer.go‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"sync"
5+
)
6+
7+
func consumer(rc chan RequestData, wg *sync.WaitGroup) {
8+
9+
//TODO: Implement your own consumner fn
10+
// The main job of consumer is to make a call to API & write the response to a file
11+
12+
for request := range rc {
13+
someUniqueFileName := "unique_file_name.json"
14+
15+
liveResponse, err := callAPI(request.Endpoints.Live, request.RequestParams)
16+
if err != nil {
17+
writeJSONStringToFile("./live/"+someUniqueFileName, liveResponse)
18+
}
19+
localResponse, err := callAPI(request.Endpoints.Local, request.RequestParams)
20+
if err != nil {
21+
writeJSONStringToFile("./local/"+someUniqueFileName, localResponse)
22+
}
23+
24+
wg.Done()
25+
}
26+
}

‎json-diff/diff.js‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const fs = require('fs');
2+
const goLivePath = './log/live'
3+
const goLocalPath = './log/local'
4+
const diffFilePath = './log/diff'
5+
const decorators = fs.readdirSync(goLivePath)
6+
7+
const dataProvider = (decorator, fileName) => {
8+
const localResponsePath = `${goLocalPath}/${decorator}/${fileName}`
9+
const liveResponsePath = `${goLivePath}/${decorator}/${fileName}`
10+
const localData = parse(read(localResponsePath))
11+
const liveData = parse(read(liveResponsePath))
12+
return [localData, liveData]
13+
}
14+
15+
const adm = () => ({ added: [], modified: [], deleted: [] })
16+
const isPrimitive = v => v !== Object(v)
17+
const intersection = (xs, ys) => xs.filter(x => ys.includes(x))
18+
const setDifference = (xs, ys) => {
19+
const common = intersection(xs, ys)
20+
return xs.filter(x => !common.includes(x))
21+
}
22+
const parse = JSON.parse
23+
const keys = Object.keys
24+
const read = path => fs.readFileSync(path, 'utf8')
25+
const uniq = xs => keys(xs.reduce((acc, x) => acc[x] = true && acc, {}))
26+
const isEmpty = xs => xs.length === 0
27+
28+
const diff = (o1, o2, path, diffAdm) => {
29+
if (!o1 || !o2) return diffAdm
30+
const [keys1, keys2] = [keys(o1), keys(o2)]
31+
const [addedKeys, deletedKeys] = [setDifference(keys2, keys1), setDifference(keys1, keys2)]
32+
diffAdm.added = [...diffAdm.added, ...addedKeys.map(k => `${path}.${k}`)]
33+
diffAdm.deleted = [...diffAdm.deleted, ...deletedKeys.map(k => `${path}.${k}`)]
34+
const modifiedKeys = intersection(keys1, keys2)
35+
diffAdm = modifiedKeys.reduce((acc, key) => {
36+
const fullKey = `${path}.${key}`
37+
const [v1, v2] = [o1[key], o2[key]]
38+
if (isPrimitive(v1) && isPrimitive(v2)) {
39+
if (v1 !== v2) {
40+
const diffKey = fullKey.split('.').slice(3).join('.')
41+
acc.modified = [...acc.modified, diffKey]
42+
}
43+
return acc
44+
} else {
45+
return diff(v1, v2, fullKey, diffAdm)
46+
}
47+
}, diffAdm)
48+
return diffAdm
49+
}
50+
51+
const writeChangesToFile = (fileName, added, modified, deleted) => {
52+
const data = `
53+
Added Keys: \n${added.reduce((acc, x) => `${acc}\n\t\t${x}`, '')}\n
54+
Modified Keys: \n${modified.reduce((acc, x) => `${acc}\n\t\t${x}`, '')}\n
55+
Deleted Keys: \n${deleted.reduce((acc, x) => `${acc}\n\t\t${x}`, '')}\n
56+
`;
57+
fs.writeFileSync(`${diffFilePath}/${fileName}.txt`, data)
58+
};
59+
60+
decorators.forEach(decorator => {
61+
const files = fs.readdirSync(`${goLivePath}/${decorator}/`)
62+
files.forEach(file => {
63+
const [localData, liveData] = dataProvider(decorator, file)
64+
let changes = diff(localData, liveData, '', adm())
65+
let { added, modified, deleted } = changes
66+
67+
if (!isEmpty(added) || !isEmpty(modified) || !isEmpty(deleted)) {
68+
writeChangesToFile(`${decorator}/${file}`, added, modified, deleted)
69+
}
70+
})
71+
});

‎json-diff/file.go‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
)
9+
10+
func prettyfy(jstr string) (string, error) {
11+
b := []byte(jstr)
12+
var out bytes.Buffer
13+
err := json.Indent(&out, b, "", " ")
14+
return out.String(), err
15+
}
16+
17+
func writeJSONStringToFile(filename, jsonString string) {
18+
fn, err := os.Create(filename)
19+
defer fn.Close()
20+
prettyJSON, err := prettyfy(jsonString)
21+
if err != nil {
22+
fmt.Println(err)
23+
}
24+
fn.WriteString(prettyJSON)
25+
}

‎json-diff/main.go‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"sync"
8+
"time"
9+
)
10+
11+
//Endpoints - contains live & local endpoints
12+
type Endpoints struct {
13+
Live string
14+
Local string
15+
}
16+
17+
//RequestParams - contains params needed to make http request
18+
type RequestParams struct {
19+
QueryParams map[string]string
20+
Headers map[string]string
21+
Body map[string]interface{}
22+
}
23+
24+
//RequestData - contains data needs to make request to endpoints
25+
type RequestData struct {
26+
Endpoints *Endpoints
27+
RequestParams *RequestParams
28+
}
29+
30+
func initializeDirectories() {
31+
os.MkdirAll("./log/local", os.ModePerm)
32+
os.MkdirAll("./log/live", os.ModePerm)
33+
os.MkdirAll("./log/diff", os.ModePerm)
34+
}
35+
36+
func main() {
37+
initializeDirectories()
38+
39+
var wg sync.WaitGroup
40+
wg.Add(1)
41+
42+
// Buffered channel so that producer and consumers don't block,
43+
// feel free to increase the buffer size
44+
rc := make(chan RequestData, 100)
45+
46+
start := time.Now()
47+
48+
go producer(rc, &wg)
49+
50+
go consumer(rc, &wg)
51+
go consumer(rc, &wg)
52+
go consumer(rc, &wg)
53+
go consumer(rc, &wg)
54+
55+
fmt.Println("Waiting...")
56+
57+
wg.Wait()
58+
59+
fmt.Println("Running json-diff...")
60+
out, err := exec.Command("sh", "-c", "node diff.js").Output()
61+
fmt.Println("exec:", out, err)
62+
63+
fmt.Println("Done...")
64+
fmt.Println(time.Now().Sub(start))
65+
66+
}

‎json-diff/producer.go‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import "sync"
4+
5+
func producer(rc chan RequestData, wg *sync.WaitGroup) {
6+
defer wg.Done()
7+
8+
//TODO: implement your own producer fn
9+
// The main job of producer is to genetate different combinations of
10+
// requests and write it to the Request Channle (rc)
11+
12+
endpoints := &Endpoints{
13+
Live: "http://local.api.endpoint",
14+
Local: "https://live.api.endpoint",
15+
}
16+
17+
queryParams := map[string]string{
18+
"param1": "value1",
19+
"param2": "value2",
20+
}
21+
22+
headers := map[string]string{
23+
"x-api-token": "XXXXXXXXX",
24+
"x-device": "YYYYYYYYYY",
25+
}
26+
27+
params := &RequestParams{
28+
QueryParams: queryParams,
29+
Headers: headers,
30+
}
31+
32+
//This is just a single request, you could have multiple requests in a loop
33+
rc <- RequestData{
34+
Endpoints: endpoints,
35+
RequestParams: params,
36+
}
37+
//Don't forget to add to the wait group after writing to the channel
38+
wg.Add(1)
39+
40+
//Note: Don't forget to close the channel once you are done.
41+
close(rc)
42+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /