Skip to content

Commit

Permalink
added json query features
Browse files Browse the repository at this point in the history
  • Loading branch information
JubaerHossain committed Sep 10, 2024
1 parent a3cace5 commit f4d25d7
Show file tree
Hide file tree
Showing 10 changed files with 469 additions and 21 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ go 1.22

require (
github.com/aws/aws-sdk-go v1.54.10
github.com/elgs/gosplitargs v0.0.0-20230310130726-7d16e488436a
github.com/gertd/go-pluralize v0.2.1
github.com/go-playground/validator v9.31.0+incompatible
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.8.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/jackc/pgx/v5 v5.6.0
github.com/schollz/progressbar/v3 v3.14.3
github.com/spf13/afero v1.11.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/elgs/gosplitargs v0.0.0-20230310130726-7d16e488436a h1:vO9yvZPJqJMJJsFdaD+sLkwjZfxzr/0fm22AnW8R6ms=
github.com/elgs/gosplitargs v0.0.0-20230310130726-7d16e488436a/go.mod h1:1zHirgOZTAY00iwXKAX0NsVKjwkwjSr1P+yqciHfRH0=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
Expand All @@ -51,6 +53,8 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down
7 changes: 6 additions & 1 deletion pkg/core/filesystem/fileupload.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"
"path/filepath"
"time"

"github.com/JubaerHossain/rootx/pkg/core/config"

Expand Down Expand Up @@ -63,7 +64,11 @@ func (s *FileUploadService) generateUniqueFileName(originalName string) string {
name := originalName[:len(originalName)-len(ext)]

// Create a new file name by appending the unique before the extension
newFileName := fmt.Sprintf("%s_%s%s", name, rootUtils.GenerateUniqueNumber(8), ext)
uniqueNumber, err := rootUtils.GenerateUniqueNumber(8)
if err != nil {
uniqueNumber = time.Now().Format("20060102150405")
}
newFileName := fmt.Sprintf("%s_%s%s", name, uniqueNumber, ext)

return newFileName
}
Expand Down
190 changes: 190 additions & 0 deletions pkg/jsonquery/jsonqury.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package jsonQuery

import (
"encoding/json"
"fmt"
"os"
"strconv"
"strings"

"github.com/JubaerHossain/rootx/pkg/splitters"
)

// JQ (JSON Query) struct
type JQ struct {
Data any
}

// NewFileQuery - Create a new &JQ from a JSON file.
func NewFileQuery(jsonFile string) (*JQ, error) {
raw, err := os.ReadFile(jsonFile)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
var data any
if err := json.Unmarshal(raw, &data); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON from file: %w", err)
}
return &JQ{Data: data}, nil
}

// NewStringQuery - Create a new &JQ from a raw JSON string.
func NewStringQuery(jsonString string) (*JQ, error) {
var data any
if err := json.Unmarshal([]byte(jsonString), &data); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON string: %w", err)
}
return &JQ{Data: data}, nil
}

// NewQuery - Create a &JQ from any parsed by json.Unmarshal
func NewQuery(jsonObject any) *JQ {
return &JQ{Data: jsonObject}
}

// Query - queries against the JSON with the expression passed in. The exp is separated by dots (".")
func (jq *JQ) Query(exp string) (any, error) {
if exp == "." {
return jq.Data, nil
}

paths, err := splitters.SplitArgs(exp, ".", false)
if err != nil {
return nil, fmt.Errorf("failed to split query expression: %w", err)
}

var context = jq.Data
for _, path := range paths {
if isArrayPath(path) {
index, err := parseArrayIndex(path)
if err != nil {
return nil, err
}

arr, ok := context.([]any)
if !ok {
return nil, fmt.Errorf("%s is not an array", path)
}
if index >= len(arr) {
return nil, fmt.Errorf("index %d out of range in %s", index, path)
}
context = arr[index]
} else {
obj, ok := context.(map[string]any)
if !ok {
return nil, fmt.Errorf("%s is not a JSON object", path)
}
val, exists := obj[path]
if !exists {
return nil, fmt.Errorf("%s does not exist in the JSON object", path)
}
context = val
}
}
return context, nil
}

// QueryToMap - Queries and converts the result to a map[string]any
func (jq *JQ) QueryToMap(exp string) (map[string]any, error) {
result, err := jq.Query(exp)
if err != nil {
return nil, fmt.Errorf("failed to query: %w", err)
}

ret, ok := result.(map[string]any)
if !ok {
return nil, fmt.Errorf("expected a JSON object but got a different type for: %s", exp)
}
return ret, nil
}

// QueryToArray - Queries and converts the result to a slice of any ([]any)
func (jq *JQ) QueryToArray(exp string) ([]any, error) {
result, err := jq.Query(exp)
if err != nil {
return nil, fmt.Errorf("failed to query: %w", err)
}

ret, ok := result.([]any)
if !ok {
return nil, fmt.Errorf("expected an array but got a different type for: %s", exp)
}
return ret, nil
}

// QueryToString - Queries and converts the result to a string
func (jq *JQ) QueryToString(exp string) (string, error) {
result, err := jq.Query(exp)
if err != nil {
return "", fmt.Errorf("failed to query: %w", err)
}

ret, ok := result.(string)
if !ok {
return "", fmt.Errorf("expected a string but got a different type for: %s", exp)
}
return ret, nil
}

// QueryToInt64 - Queries and converts the result to an int64
func (jq *JQ) QueryToInt64(exp string) (int64, error) {
result, err := jq.Query(exp)
if err != nil {
return 0, fmt.Errorf("failed to query: %w", err)
}

switch v := result.(type) {
case float64:
return int64(v), nil
case string:
ret, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to convert string to int64: %w", err)
}
return ret, nil
default:
return 0, fmt.Errorf("expected an int64-compatible value but got: %v", result)
}
}

// QueryToFloat64 - Queries and converts the result to a float64
func (jq *JQ) QueryToFloat64(exp string) (float64, error) {
result, err := jq.Query(exp)
if err != nil {
return 0, fmt.Errorf("failed to query: %w", err)
}

ret, ok := result.(float64)
if !ok {
return 0, fmt.Errorf("expected a float64 but got a different type for: %s", exp)
}
return ret, nil
}

// QueryToBool - Queries and converts the result to a boolean
func (jq *JQ) QueryToBool(exp string) (bool, error) {
result, err := jq.Query(exp)
if err != nil {
return false, fmt.Errorf("failed to query: %w", err)
}

ret, ok := result.(bool)
if !ok {
return false, fmt.Errorf("expected a boolean but got a different type for: %s", exp)
}
return ret, nil
}

// Helper functions

func isArrayPath(path string) bool {
return strings.HasPrefix(path, "[") && strings.HasSuffix(path, "]")
}

func parseArrayIndex(path string) (int, error) {
index, err := strconv.Atoi(path[1 : len(path)-1])
if err != nil {
return 0, fmt.Errorf("invalid array index %s: %w", path, err)
}
return index, nil
}
108 changes: 108 additions & 0 deletions pkg/splitters/splitters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package splitters

import (
"errors"
"strings"
)

// SplitArgs splits a string based on the given separator while handling quotes and optional comment support.
func SplitArgs(input, separator string, keepQuotes bool) ([]string, error) {
return splitArgs(input, separator, keepQuotes, "")
}

// SplitSQL splits SQL statements, supporting custom comment markers and preserving quotes.
func SplitSQL(input, separator string, keepQuotes bool) ([]string, error) {
return splitArgs(input, separator, keepQuotes, "--")
}

// splitArgs handles string splitting, optionally preserving quotes and skipping comments.
func splitArgs(input, separator string, keepQuotes bool, commentSign string) ([]string, error) {
// Ensure the separator is not empty
if separator == "" {
return nil, errors.New("separator cannot be empty")
}

singleQuoteOpen := false
doubleQuoteOpen := false
commentSignOpen := false
commentSignIndex := 0
separatorIndex := 0

var tokenBuffer strings.Builder
var result []string

// Iterate over each character in the input string
for _, char := range input {
inputChar := string(char)

// Handle newlines to reset comment state
if inputChar == "\n" {
commentSignIndex = 0
commentSignOpen = false
tokenBuffer.WriteString(inputChar)
continue
}

// Skip over characters in comment mode
if commentSignOpen {
tokenBuffer.WriteString(inputChar)
continue
}

// Handle single and double quotes
if inputChar == "'" && !doubleQuoteOpen && !commentSignOpen {
if keepQuotes {
tokenBuffer.WriteString(inputChar)
}
singleQuoteOpen = !singleQuoteOpen
continue
} else if inputChar == `"` && !singleQuoteOpen && !commentSignOpen {
if keepQuotes {
tokenBuffer.WriteString(inputChar)
}
doubleQuoteOpen = !doubleQuoteOpen
continue
}

// Handle comment markers
if !singleQuoteOpen && !doubleQuoteOpen && !commentSignOpen {
if commentSign != "" && inputChar == string(commentSign[commentSignIndex]) {
// Comment marker detected
commentSignIndex++
if commentSignIndex == len(commentSign) {
commentSignOpen = true
commentSignIndex = 0
}
tokenBuffer.WriteString(inputChar)
continue
} else {
commentSignIndex = 0
}
}

// Handle separator logic outside quotes and comments
if !singleQuoteOpen && !doubleQuoteOpen && !commentSignOpen {
if inputChar == string(separator[separatorIndex]) {
separatorIndex++
if separatorIndex == len(separator) {
// Separator fully matched
result = append(result, tokenBuffer.String())
tokenBuffer.Reset()
separatorIndex = 0
continue
}
} else {
// Reset separator matching state
separatorIndex = 0
}
}
tokenBuffer.WriteString(inputChar)
}

// Add remaining buffer content to the result
if tokenBuffer.Len() > 0 {
result = append(result, tokenBuffer.String())
}

return result, nil
}
Loading

0 comments on commit f4d25d7

Please sign in to comment.