Compare commits

...

5 Commits

26 changed files with 598 additions and 440 deletions

View File

@ -29,9 +29,9 @@ COPY --from=react-build /web/build/ ./internal/serv/web/build
RUN go mod vendor RUN go mod vendor
RUN make build RUN make build
RUN echo "Compressing binary, will take a bit of time..." && \ # RUN echo "Compressing binary, will take a bit of time..." && \
upx --ultra-brute -qq super-graph && \ # upx --ultra-brute -qq super-graph && \
upx -t super-graph # upx -t super-graph

View File

@ -30,12 +30,10 @@ type Config struct {
// or other database functions // or other database functions
SetUserID bool `mapstructure:"set_user_id"` SetUserID bool `mapstructure:"set_user_id"`
// DefaultAllow reverses the blocked by default behaviour for queries in // DefaultBlock ensures that in anonymous mode (role 'anon') all tables
// anonymous mode. (anon role) // are blocked from queries and mutations. To open access to tables in
// For example if the table `users` is not listed under the anon role then // anonymous mode they have to be added to the 'anon' role config.
// access to it would by default for unauthenticated queries this reverses DefaultBlock bool `mapstructure:"default_block"`
// this behavior (!!! Use with caution !!!!)
DefaultAllow bool `mapstructure:"default_allow"`
// Vars is a map of hardcoded variables that can be leveraged in your // Vars is a map of hardcoded variables that can be leveraged in your
// queries (eg variable admin_id will be $admin_id in the query) // queries (eg variable admin_id will be $admin_id in the query)
@ -57,6 +55,9 @@ type Config struct {
// Roles contains all the configuration for all the roles you want to support // Roles contains all the configuration for all the roles you want to support
// `user` and `anon` are two default roles. User role is for when a user ID is // `user` and `anon` are two default roles. User role is for when a user ID is
// available and Anon when it's not. // available and Anon when it's not.
//
// If you're using the RolesQuery config to enable atribute based acess control then
// you can add more custom roles.
Roles []Role Roles []Role
// Inflections is to add additionally singular to plural mappings // Inflections is to add additionally singular to plural mappings
@ -108,12 +109,12 @@ type Role struct {
// RoleTable struct contains role specific access control values for a database table // RoleTable struct contains role specific access control values for a database table
type RoleTable struct { type RoleTable struct {
Name string Name string
ReadOnly *bool `mapstructure:"read_only"` ReadOnly bool `mapstructure:"read_only"`
Query Query Query *Query
Insert Insert Insert *Insert
Update Update Update *Update
Delete Delete Delete *Delete
} }
// Query struct contains access control values for query operations // Query struct contains access control values for query operations
@ -122,7 +123,7 @@ type Query struct {
Filters []string Filters []string
Columns []string Columns []string
DisableFunctions bool `mapstructure:"disable_functions"` DisableFunctions bool `mapstructure:"disable_functions"`
Block *bool Block bool
} }
// Insert struct contains access control values for insert operations // Insert struct contains access control values for insert operations
@ -130,7 +131,7 @@ type Insert struct {
Filters []string Filters []string
Columns []string Columns []string
Presets map[string]string Presets map[string]string
Block *bool Block bool
} }
// Insert struct contains access control values for update operations // Insert struct contains access control values for update operations
@ -138,14 +139,59 @@ type Update struct {
Filters []string Filters []string
Columns []string Columns []string
Presets map[string]string Presets map[string]string
Block *bool Block bool
} }
// Delete struct contains access control values for delete operations // Delete struct contains access control values for delete operations
type Delete struct { type Delete struct {
Filters []string Filters []string
Columns []string Columns []string
Block *bool Block bool
}
// AddRoleTable function is a helper function to make it easy to add per-table
// row-level config
func (c *Config) AddRoleTable(role string, table string, conf interface{}) error {
var r *Role
for i := range c.Roles {
if strings.EqualFold(c.Roles[i].Name, role) {
r = &c.Roles[i]
break
}
}
if r == nil {
nr := Role{Name: role}
c.Roles = append(c.Roles, nr)
r = &nr
}
var t *RoleTable
for i := range r.Tables {
if strings.EqualFold(r.Tables[i].Name, table) {
t = &r.Tables[i]
break
}
}
if t == nil {
nt := RoleTable{Name: table}
r.Tables = append(r.Tables, nt)
t = &nt
}
switch v := conf.(type) {
case Query:
t.Query = &v
case Insert:
t.Insert = &v
case Update:
t.Update = &v
case Delete:
t.Delete = &v
default:
return fmt.Errorf("unsupported object type: %t", v)
}
return nil
} }
// ReadInConfig function reads in the config file for the environment specified in the GO_ENV // ReadInConfig function reads in the config file for the environment specified in the GO_ENV

View File

@ -90,7 +90,8 @@ func (sg *SuperGraph) initCompilers() error {
} }
sg.qc, err = qcode.NewCompiler(qcode.Config{ sg.qc, err = qcode.NewCompiler(qcode.Config{
Blocklist: sg.conf.Blocklist, DefaultBlock: sg.conf.DefaultBlock,
Blocklist: sg.conf.Blocklist,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -196,7 +196,7 @@ func addForeignKey(di *psql.DBInfo, c Column, t Table) error {
func addRoles(c *Config, qc *qcode.Compiler) error { func addRoles(c *Config, qc *qcode.Compiler) error {
for _, r := range c.Roles { for _, r := range c.Roles {
for _, t := range r.Tables { for _, t := range r.Tables {
if err := addRole(qc, r, t, c.DefaultAllow); err != nil { if err := addRole(qc, r, t, c.DefaultBlock); err != nil {
return err return err
} }
} }
@ -205,67 +205,56 @@ func addRoles(c *Config, qc *qcode.Compiler) error {
return nil return nil
} }
func addRole(qc *qcode.Compiler, r Role, t RoleTable, defaultAllow bool) error { func addRole(qc *qcode.Compiler, r Role, t RoleTable, defaultBlock bool) error {
ro := true // read-only ro := false // read-only
if defaultAllow { if defaultBlock && r.Name == "anon" {
ro = false ro = true
} }
if r.Name != "anon" { if t.ReadOnly {
ro = false ro = true
} }
if t.ReadOnly != nil { query := qcode.QueryConfig{Block: false}
ro = *t.ReadOnly insert := qcode.InsertConfig{Block: ro}
update := qcode.UpdateConfig{Block: ro}
del := qcode.DeleteConfig{Block: ro}
if t.Query != nil {
query = qcode.QueryConfig{
Limit: t.Query.Limit,
Filters: t.Query.Filters,
Columns: t.Query.Columns,
DisableFunctions: t.Query.DisableFunctions,
Block: t.Query.Block,
}
} }
blocked := struct { if t.Insert != nil {
query bool insert = qcode.InsertConfig{
insert bool Filters: t.Insert.Filters,
update bool Columns: t.Insert.Columns,
delete bool Presets: t.Insert.Presets,
}{false, ro, ro, ro} Block: t.Insert.Block,
}
if t.Query.Block != nil {
blocked.query = *t.Query.Block
}
if t.Insert.Block != nil {
blocked.insert = *t.Insert.Block
}
if t.Update.Block != nil {
blocked.update = *t.Update.Block
}
if t.Delete.Block != nil {
blocked.delete = *t.Delete.Block
} }
query := qcode.QueryConfig{ if t.Update != nil {
Limit: t.Query.Limit, update = qcode.UpdateConfig{
Filters: t.Query.Filters, Filters: t.Update.Filters,
Columns: t.Query.Columns, Columns: t.Update.Columns,
DisableFunctions: t.Query.DisableFunctions, Presets: t.Update.Presets,
Block: blocked.query, Block: t.Update.Block,
}
} }
insert := qcode.InsertConfig{ if t.Delete != nil {
Filters: t.Insert.Filters, del = qcode.DeleteConfig{
Columns: t.Insert.Columns, Filters: t.Delete.Filters,
Presets: t.Insert.Presets, Columns: t.Delete.Columns,
Block: blocked.insert, Block: t.Delete.Block,
} }
update := qcode.UpdateConfig{
Filters: t.Update.Filters,
Columns: t.Update.Columns,
Presets: t.Update.Presets,
Block: blocked.update,
}
del := qcode.DeleteConfig{
Filters: t.Delete.Filters,
Columns: t.Delete.Columns,
Block: blocked.delete,
} }
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{ return qc.AddRole(r.Name, t.Name, qcode.TRConfig{

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"sort" "sort"
"strings" "strings"
@ -35,6 +36,7 @@ type List struct {
type Config struct { type Config struct {
CreateIfNotExists bool CreateIfNotExists bool
Persist bool Persist bool
Log *log.Logger
} }
func New(filename string, conf Config) (*List, error) { func New(filename string, conf Config) (*List, error) {
@ -80,6 +82,12 @@ func New(filename string, conf Config) (*List, error) {
} else { } else {
al.filepath = filename al.filepath = filename
} }
if file, err := os.OpenFile(al.filepath, os.O_RDONLY|os.O_CREATE, 0644); err != nil {
return nil, err
} else {
file.Close()
}
} }
var err error var err error
@ -89,8 +97,10 @@ func New(filename string, conf Config) (*List, error) {
go func() { go func() {
for v := range al.saveChan { for v := range al.saveChan {
if err = al.save(v); err != nil { err := al.save(v)
break
if err != nil && conf.Log != nil {
conf.Log.Println("WRN allow list save:", err)
} }
} }
}() }()

View File

@ -55,19 +55,6 @@ func TestSuperGraph(t *testing.T, db *sql.DB, before func(t *testing.T)) {
config.AllowListFile = "./allow.list" config.AllowListFile = "./allow.list"
config.RolesQuery = `SELECT * FROM users WHERE id = $user_id` config.RolesQuery = `SELECT * FROM users WHERE id = $user_id`
blockFalse := false
config.Roles = []core.Role{
core.Role{
Name: "anon",
Tables: []core.RoleTable{
core.RoleTable{Name: "users", ReadOnly: &blockFalse, Query: core.Query{Limit: 100}},
core.RoleTable{Name: "product", ReadOnly: &blockFalse, Query: core.Query{Limit: 100}},
core.RoleTable{Name: "line_item", ReadOnly: &blockFalse, Query: core.Query{Limit: 100}},
},
},
}
sg, err := core.NewSuperGraph(&config, db) sg, err := core.NewSuperGraph(&config, db)
require.NoError(t, err) require.NoError(t, err)
ctx := context.Background() ctx := context.Background()

View File

@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"strconv"
"strings" "strings"
"github.com/dosco/super-graph/core/internal/qcode" "github.com/dosco/super-graph/core/internal/qcode"
@ -1355,23 +1356,5 @@ func squoted(w io.Writer, identifier string) {
const charset = "0123456789" const charset = "0123456789"
func int32String(w io.Writer, val int32) { func int32String(w io.Writer, val int32) {
if val < 10 { io.WriteString(w, strconv.FormatInt(int64(val), 10))
w.Write([]byte{charset[val]})
return
}
temp := int32(0)
val2 := val
for val2 > 0 {
temp *= 10
temp += val2 % 10
val2 = int32(float64(val2 / 10))
}
val3 := temp
for val3 > 0 {
d := val3 % 10
val3 /= 10
w.Write([]byte{charset[d]})
}
} }

View File

@ -0,0 +1,11 @@
goos: darwin
goarch: amd64
pkg: github.com/dosco/super-graph/core/internal/qcode
BenchmarkQCompile-16 120888 9236 ns/op 3755 B/op 28 allocs/op
BenchmarkQCompileP-16 502248 2620 ns/op 3795 B/op 28 allocs/op
BenchmarkParse-16 128370 9294 ns/op 3902 B/op 18 allocs/op
BenchmarkParseP-16 575752 2340 ns/op 3903 B/op 18 allocs/op
BenchmarkSchemaParse-16 212048 5779 ns/op 3968 B/op 57 allocs/op
BenchmarkSchemaParseP-16 630918 1686 ns/op 3968 B/op 57 allocs/op
PASS
ok github.com/dosco/super-graph/core/internal/qcode 7.710s

View File

@ -6,7 +6,8 @@ import (
) )
type Config struct { type Config struct {
Blocklist []string DefaultBlock bool
Blocklist []string
} }
type QueryConfig struct { type QueryConfig struct {

View File

@ -172,6 +172,8 @@ const (
type Compiler struct { type Compiler struct {
tr map[string]map[string]*trval tr map[string]map[string]*trval
bl map[string]struct{} bl map[string]struct{}
defBlock bool
} }
var expPool = sync.Pool{ var expPool = sync.Pool{
@ -179,7 +181,7 @@ var expPool = sync.Pool{
} }
func NewCompiler(c Config) (*Compiler, error) { func NewCompiler(c Config) (*Compiler, error) {
co := &Compiler{} co := &Compiler{defBlock: c.DefaultBlock}
co.tr = make(map[string]map[string]*trval) co.tr = make(map[string]map[string]*trval)
co.bl = make(map[string]struct{}, len(c.Blocklist)) co.bl = make(map[string]struct{}, len(c.Blocklist))
@ -358,7 +360,7 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
} }
} else if role == "anon" { } else if role == "anon" {
skipRender = true skipRender = com.defBlock
} }
selects = append(selects, Select{ selects = append(selects, Select{

View File

@ -187,15 +187,14 @@ func (sg *SuperGraph) initAllowList() error {
var ac allow.Config var ac allow.Config
var err error var err error
if len(sg.conf.AllowListFile) == 0 { if sg.conf.AllowListFile == "" {
sg.conf.UseAllowList = false sg.conf.AllowListFile = "allow.list"
sg.log.Printf("WRN allow list disabled no file specified")
} }
// When list is not eabled it is still created and // When list is not eabled it is still created and
// and new queries are saved to it. // and new queries are saved to it.
if !sg.conf.UseAllowList { if !sg.conf.UseAllowList {
ac = allow.Config{CreateIfNotExists: true, Persist: true} ac = allow.Config{CreateIfNotExists: true, Persist: true, Log: sg.log}
} }
sg.allowList, err = allow.New(sg.conf.AllowListFile, ac) sg.allowList, err = allow.New(sg.conf.AllowListFile, ac)

249
docs/website/docs/seed.md Normal file
View File

@ -0,0 +1,249 @@
---
id: seed
title: Database Seeding
sidebar_label: Seed Scripts
---
While developing it's often useful to be able to have fake data available in the database. Fake data can help with building the UI and save you time when trying to get the GraphQL query correct. Super Graph has the ability do this for you. All you have to do is write a seed script `config/seed.js` (In Javascript) and use the `db:seed` command line option. Below is an example of kind of things you can do in a seed script.
## Creating fake users
Since all mutations and queries are in standard GraphQL you can use all the features available in Super Graph GraphQL.
```javascript
var users = [];
for (i = 0; i < 20; i++) {
var data = {
slug: util.make_slug(fake.first_name() + "-" + fake.last_name()),
first_name: fake.first_name(),
last_name: fake.last_name(),
picture_url: fake.avatar_url(),
email: fake.email(),
bio: fake.sentence(10),
};
var res = graphql(" \
mutation { \
user(insert: $data) { \
id \
} \
}", { data: data });
users.push(res.user);
}
```
## Inserting the users fake blog posts
Another example highlighting how the `connect` syntax of Super Graph GraphQL can be used to connect inserted posts
to random users that were previously created. For futher details checkout the [seed script](/seed) documentation.
```javascript
var posts = [];
for (i = 0; i < 1500; i++) {
var user.id = users[Math.floor(Math.random() * 10)];
var data = {
slug: util.make_slug(fake.sentence(3) + i),
body: fake.sentence(100),
published: true,
thread: {
connect: { user: user.id }
}
}
var res = graphql(" \
mutation { \
post(insert: $data) { \
id \
} \
}",
{ data: data },
{ user_id: u.id })
posts.push(res.post.slug)
}
```
## Insert a large number of rows efficiently
This feature uses the `COPY` functionality available in Postgres this is the best way to
insert a large number of rows into a table. The `import_csv` function reads in a CSV file using the first
line of the file as column names.
```javascript
import_csv("post_tags", "./tags.csv");
```
## A list of fake data functions available to you.
```
person
name
name_prefix
name_suffix
first_name
last_name
gender
ssn
contact
email
phone
phone_formatted
username
password
// Address
address
city
country
country_abr
state
state_abr
street
street_name
street_number
street_prefix
street_suffix
zip
latitude
latitude_in_range
longitude
longitude_in_range
// Beer
beer_alcohol
beer_hop
beer_ibu
beer_blg
beer_malt
beer_name
beer_style
beer_yeast
// Cars
car
car_type
car_maker
car_model
// Text
word
sentence
paragraph
question
quote
// Misc
generate
boolean
uuid
// Colors
color
hex_color
rgb_color
safe_color
// Internet
url
image_url
avatar_url
domain_name
domain_suffix
ipv4_address
ipv6_address
http_method
user_agent
user_agent_firefox
user_agent_chrome
user_agent_opera
user_agent_safari
// Date / Time
date
date_range
nano_second
second
minute
hour
month
day
weekday
year
timezone
timezone_abv
timezone_full
timezone_offset
// Payment
price
credit_card
credit_card_cvv
credit_card_number
credit_card_type
currency
currency_long
currency_short
// Company
bs
buzzword
company
company_suffix
job
job_description
job_level
job_title
// Hacker
hacker_abbreviation
hacker_adjective
hacker_noun
hacker_phrase
hacker_verb
//Hipster
hipster_word
hipster_paragraph
hipster_sentence
// File
file_extension
file_mine_type
// Numbers
number
numerify
int8
int16
int32
int64
uint8
uint16
uint32
uint64
float32
float32_range
float64
float64_range
shuffle_ints
mac_address
// String
digit
letter
lexify
rand_string
numerify
```
## Some more utility functions
```
shuffle_strings(string_array)
make_slug(text)
make_slug_lang(text, lang)
```

View File

@ -96,179 +96,6 @@ var post_count = import_csv("posts", "posts.csv");
You can generate the following fake data for your seeding purposes. Below is the list of fake data functions supported by the built-in fake data library. For example `fake.image_url()` will generate a fake image url or `fake.shuffle_strings(['hello', 'world', 'cool'])` will generate a randomly shuffled version of that array of strings or `fake.rand_string(['hello', 'world', 'cool'])` will return a random string from the array provided. You can generate the following fake data for your seeding purposes. Below is the list of fake data functions supported by the built-in fake data library. For example `fake.image_url()` will generate a fake image url or `fake.shuffle_strings(['hello', 'world', 'cool'])` will generate a randomly shuffled version of that array of strings or `fake.rand_string(['hello', 'world', 'cool'])` will return a random string from the array provided.
```
// Person
person
name
name_prefix
name_suffix
first_name
last_name
gender
ssn
contact
email
phone
phone_formatted
username
password
// Address
address
city
country
country_abr
state
state_abr
status_code
street
street_name
street_number
street_prefix
street_suffix
zip
latitude
latitude_in_range
longitude
longitude_in_range
// Beer
beer_alcohol
beer_hop
beer_ibu
beer_blg
beer_malt
beer_name
beer_style
beer_yeast
// Cars
car
car_type
car_maker
car_model
// Text
word
sentence
paragraph
question
quote
// Misc
generate
boolean
uuid
// Colors
color
hex_color
rgb_color
safe_color
// Internet
url
image_url
domain_name
domain_suffix
ipv4_address
ipv6_address
simple_status_code
http_method
user_agent
user_agent_firefox
user_agent_chrome
user_agent_opera
user_agent_safari
// Date / Time
date
date_range
nano_second
second
minute
hour
month
day
weekday
year
timezone
timezone_abv
timezone_full
timezone_offset
// Payment
price
credit_card
credit_card_cvv
credit_card_number
credit_card_number_luhn
credit_card_type
currency
currency_long
currency_short
// Company
bs
buzzword
company
company_suffix
job
job_description
job_level
job_title
// Hacker
hacker_abbreviation
hacker_adjective
hacker_ingverb
hacker_noun
hacker_phrase
hacker_verb
//Hipster
hipster_word
hipster_paragraph
hipster_sentence
// File
file_extension
file_mine_type
// Numbers
number
numerify
int8
int16
int32
int64
uint8
uint16
uint32
uint64
float32
float32_range
float64
float64_range
shuffle_ints
mac_address
//String
digit
letter
lexify
shuffle_strings
numerify
```
Other utility functions
```
shuffle_strings(string_array)
make_slug(text)
make_slug_lang(text, lang)
```
### Migrations ### Migrations
Easy database migrations is the most important thing when building products backend by a relational database. We make it super easy to manage and migrate your database. Easy database migrations is the most important thing when building products backend by a relational database. We make it super easy to manage and migrate your database.

View File

@ -11,6 +11,7 @@ module.exports = {
"security", "security",
"telemetry", "telemetry",
"config", "config",
"seed",
"deploy", "deploy",
"internals", "internals",
], ],

1
go.mod
View File

@ -29,6 +29,7 @@ require (
github.com/openzipkin/zipkin-go v0.2.2 github.com/openzipkin/zipkin-go v0.2.2
github.com/pelletier/go-toml v1.7.0 // indirect github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/common v0.4.0
github.com/rs/cors v1.7.0 github.com/rs/cors v1.7.0
github.com/spf13/afero v1.2.2 // indirect github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cast v1.3.1 // indirect

5
go.sum
View File

@ -35,7 +35,9 @@ github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 h1:+qz9Ga6l6lKw6fgv
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3/go.mod h1:FlkD11RtgMTYjVuBnb7cxoHmQGqvPpCsr2atC88nl/M= github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3/go.mod h1:FlkD11RtgMTYjVuBnb7cxoHmQGqvPpCsr2atC88nl/M=
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.15.27 h1:i75BxN4Es/8rTVQbEKAP1WCiIhhz635xTNeDdZJRAXQ= github.com/aws/aws-sdk-go v1.15.27 h1:i75BxN4Es/8rTVQbEKAP1WCiIhhz635xTNeDdZJRAXQ=
@ -220,6 +222,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -319,6 +322,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@ -543,6 +547,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -156,7 +156,7 @@ func cmdVersion(cmd *cobra.Command, args []string) {
func BuildDetails() string { func BuildDetails() string {
if len(version) == 0 { if len(version) == 0 {
return fmt.Sprintf(` return `
Super Graph (unknown version) Super Graph (unknown version)
For documentation, visit https://supergraph.dev For documentation, visit https://supergraph.dev
@ -166,7 +166,7 @@ To build with version information please use the Makefile
Licensed under the Apache Public License 2.0 Licensed under the Apache Public License 2.0
Copyright 2020, Vikram Rangnekar Copyright 2020, Vikram Rangnekar
`) `
} }
return fmt.Sprintf(` return fmt.Sprintf(`

View File

@ -17,6 +17,8 @@ import (
"github.com/dop251/goja" "github.com/dop251/goja"
"github.com/dosco/super-graph/core" "github.com/dosco/super-graph/core"
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/stdlib"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -27,6 +29,7 @@ func cmdDBSeed(cmd *cobra.Command, args []string) {
log.Fatalf("ERR failed to read config: %s", err) log.Fatalf("ERR failed to read config: %s", err)
} }
conf.Production = false conf.Production = false
conf.DefaultBlock = false
db, err = initDB(conf, true, false) db, err = initDB(conf, true, false)
if err != nil { if err != nil {
@ -51,7 +54,7 @@ func cmdDBSeed(cmd *cobra.Command, args []string) {
vm := goja.New() vm := goja.New()
vm.Set("graphql", graphQLFn) vm.Set("graphql", graphQLFn)
//vm.Set("import_csv", importCSV) vm.Set("import_csv", importCSV)
console := vm.NewObject() console := vm.NewObject()
console.Set("log", logFunc) //nolint: errcheck console.Set("log", logFunc) //nolint: errcheck
@ -181,34 +184,42 @@ func (c *csvSource) Err() error {
return nil return nil
} }
// func importCSV(table, filename string) int64 { func importCSV(table, filename string) int64 {
// if filename[0] != '/' { if filename[0] != '/' {
// filename = path.Join(conf.ConfigPathUsed(), filename) filename = path.Join(confPath, filename)
// } }
// s, err := NewCSVSource(filename) s, err := NewCSVSource(filename)
// if err != nil { if err != nil {
// log.Fatalf("ERR %s", err) log.Fatalf("ERR %v", err)
// } }
// var cols []string var cols []string
// colval, _ := s.Values() colval, _ := s.Values()
// for _, c := range colval { for _, c := range colval {
// cols = append(cols, c.(string)) cols = append(cols, c.(string))
// } }
// n, err := db.Exec(fmt.Sprintf("COPY %s FROM STDIN WITH "), conn, err := stdlib.AcquireConn(db)
// cols, if err != nil {
// s) log.Fatalf("ERR %v", err)
}
//nolint: errcheck
defer stdlib.ReleaseConn(db, conn)
// if err != nil { n, err := conn.CopyFrom(
// err = fmt.Errorf("%w (line no %d)", err, s.i) context.Background(),
// log.Fatalf("ERR %s", err) pgx.Identifier{table},
// } cols,
s)
// return n if err != nil {
// } log.Fatalf("ERR %v", fmt.Errorf("%w (line no %d)", err, s.i))
}
return n
}
//nolint: errcheck //nolint: errcheck
func logFunc(args ...interface{}) { func logFunc(args ...interface{}) {
@ -377,11 +388,6 @@ func setFakeFuncs(f *goja.Object) {
f.Set("hipster_paragraph", gofakeit.HipsterParagraph) f.Set("hipster_paragraph", gofakeit.HipsterParagraph)
f.Set("hipster_sentence", gofakeit.HipsterSentence) f.Set("hipster_sentence", gofakeit.HipsterSentence)
//Languages
//f.Set("language", gofakeit.Language)
//f.Set("language_abbreviation", gofakeit.LanguageAbbreviation)
//f.Set("language_abbreviation", gofakeit.LanguageAbbreviation)
// File // File
f.Set("file_extension", gofakeit.FileExtension) f.Set("file_extension", gofakeit.FileExtension)
f.Set("file_mine_type", gofakeit.FileMimeType) f.Set("file_mine_type", gofakeit.FileMimeType)
@ -410,8 +416,6 @@ func setFakeFuncs(f *goja.Object) {
f.Set("lexify", gofakeit.Lexify) f.Set("lexify", gofakeit.Lexify)
f.Set("rand_string", getRandValue) f.Set("rand_string", getRandValue)
f.Set("numerify", gofakeit.Numerify) f.Set("numerify", gofakeit.Numerify)
//f.Set("programming_language", gofakeit.ProgrammingLanguage)
} }
//nolint: errcheck //nolint: errcheck

View File

@ -69,6 +69,8 @@ func newViper(configPath, configFile string) *viper.Viper {
vi.SetDefault("auth_fail_block", "always") vi.SetDefault("auth_fail_block", "always")
vi.SetDefault("seed_file", "seed.js") vi.SetDefault("seed_file", "seed.js")
vi.SetDefault("default_block", true)
vi.SetDefault("database.type", "postgres") vi.SetDefault("database.type", "postgres")
vi.SetDefault("database.host", "localhost") vi.SetDefault("database.host", "localhost")
vi.SetDefault("database.port", 5432) vi.SetDefault("database.port", 5432)

View File

@ -15,7 +15,6 @@ import (
"contrib.go.opencensus.io/integrations/ocsql" "contrib.go.opencensus.io/integrations/ocsql"
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/stdlib" "github.com/jackc/pgx/v4/stdlib"
//_ "github.com/jackc/pgx/v4/stdlib"
) )
const ( const (

View File

@ -3,6 +3,7 @@ package jsn
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"io" "io"
) )
@ -68,7 +69,12 @@ func Clear(w *bytes.Buffer, v []byte) error {
} }
io := int(dec.InputOffset()) io := int(dec.InputOffset())
w.Write(v[io-len(v1)-2 : io]) s := io - len(v1) - 2
if io <= s || s <= 0 {
return errors.New("invalid json")
}
w.Write(v[s:io])
w.WriteString(`:`) w.WriteString(`:`)
isValue = true isValue = true

View File

@ -2,10 +2,55 @@
package jsn package jsn
import (
"bytes"
"errors"
)
func Fuzz(data []byte) int { func Fuzz(data []byte) int {
if err := unifiedTest(data); err != nil { c := 0
return 0
if err := Validate(string(data)); err == nil {
c = 1
} }
return 1 if err := fuzzTest(data); err == nil {
c = 1
}
return c
}
func fuzzTest(data []byte) error {
err1 := Validate(string(data))
var b1 bytes.Buffer
err2 := Filter(&b1, data, []string{"id", "full_name", "embed"})
path1 := [][]byte{[]byte("data"), []byte("users")}
Strip(data, path1)
from := []Field{
{[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)},
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
}
to := []Field{
{[]byte("__twitter_id"), []byte(`"1234567890"`)},
{[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)},
}
var b2 bytes.Buffer
err3 := Replace(&b2, data, from, to)
Keys(data)
var b3 bytes.Buffer
err4 := Clear(&b3, data)
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
return errors.New("there was an error")
}
return nil
} }

View File

@ -1,60 +1,76 @@
package jsn // +build gofuzz
package jsn_test
import ( import (
"testing" "testing"
"github.com/dosco/super-graph/jsn"
) )
var ret int
func TestFuzzCrashers(t *testing.T) { func TestFuzzCrashers(t *testing.T) {
var crashers = []string{ var crashers = []string{
"00\"0000\"0{", /*
"6\",\n\t\t\t\"something\": " + "00\"0000\"0{",
"null\n\t\t},\n\t\t{\n\t\t\t\"id" + "6\",\n\t\t\t\"something\": " +
"\": 12,\n\t\t\t\"full_name" + "null\n\t\t},\n\t\t{\n\t\t\t\"id" +
"\": \"Brenton Bauch Ph" + "\": 12,\n\t\t\t\"full_name" +
"D\",\n\t\t\t\"email\": \"ren" + "\": \"Brenton Bauch Ph" +
"ee@miller.co\",\n\t\t\t\"_" + "D\",\n\t\t\t\"email\": \"ren" +
"_twitter_id\": 1\n\t\t}," + "ee@miller.co\",\n\t\t\t\"_" +
"\n\t\t{\n\t\t\t\"id\": 13,\n\t\t" + "_twitter_id\": 1\n\t\t}," +
"\t\"full_name\": \"Daine" + "\n\t\t{\n\t\t\t\"id\": 13,\n\t\t" +
" Gleichner\",\n\t\t\t\"ema" + "\t\"full_name\": \"Daine" +
"il\": \"andrea@gmail.c" + " Gleichner\",\n\t\t\t\"ema" +
"om\",\n\t\t\t\"__twitter_i" + "il\": \"andrea@gmail.c" +
"d\": \"\",\n\t\t\t\"id__twit" + "om\",\n\t\t\t\"__twitter_i" +
"ter_id\": \"NOOO\",\n\t\t\t" + "d\": \"\",\n\t\t\t\"id__twit" +
"\"work_email\": \"andre" + "ter_id\": \"NOOO\",\n\t\t\t" +
"a@nienow.co\"\n\t\t}\n\t]}" + "\"work_email\": \"andre" +
"\n\t}", "a@nienow.co\"\n\t\t}\n\t]}" +
"0000\"0000\"0{", "\n\t}",
"0000\"\"{", "0000\"0000\"0{",
"0000\"000\"{", "0000\"\"{",
"0\"\"{", "0000\"000\"{",
"\"0\"{", "0\"\"{",
"000\"0\"{", "\"0\"{",
"0\"0000\"0{", "000\"0\"{",
"000\"\"{", "0\"0000\"0{",
"0\"00\"{", "000\"\"{",
"000\"0000\"0{", "0\"00\"{",
"000\"00\"{", "000\"0000\"0{",
"\"\"{", "000\"00\"{",
"0\"0000\"{", "\"\"{",
"\"000\"00{", "0\"0000\"{",
"0000\"00\"{", "\"000\"00{",
"00\"0\"{", "0000\"00\"{",
"0\"0\"{", "00\"0\"{",
"000\"0000\"{", "0\"0\"{",
"00\"0000\"{", "000\"0000\"{",
"0000\"0000\"{", "00\"0000\"{",
"\"000\"{", "0000\"0000\"{",
"00\"00\"{", "\"000\"{",
"00\"0000\"00{", "00\"00\"{",
"0\"0000\"00{", "00\"0000\"00{",
"00\"\"{", "0\"0000\"00{",
"0000\"0\"{", "00\"\"{",
"000\"000\"{", "0000\"0\"{",
"\"00000000\"{", "000\"000\"{",
"\"00000000\"{",
`0000"00"00000000"000000000"00"000000000000000"00000"00000": "00"0"__twitter_id": [{ "name": "hello" }, { "name": "world"}]`,
*/
`0000"000000000000000000000000000000000000"00000000"000000000"00"000000000000000"00000"00000": "00000000000000"00000"__twitter_id": [{ "name": "hello" }, { "name": "world"}]`,
`00"__twitter_id":[{ "name": "hello" }, { "name": "world"}]`,
"\"\xb0\xef\xbd\xe3\xbd\xef\x99\xe3\xbd\xef\xbd\xef\xbd\xef\xbd\xe5\x99\xe3\xbd" +
"\xef\x99\xe3\"",
"\"\xef\xe3\xef\xe3\xe3\xe3\xef\xe3\xe3\xef\xe3\xef\xe3\xe3\xe3\xef\xe3\xef\xe3" +
"\xe3\xef\xef\xef\xe5\xe3\xef\xe3\xc6\xef\xef\xef\xe5\xe3\xef\xe3\xc6\xef\xef\"",
} }
for _, f := range crashers { for _, f := range crashers {
_ = unifiedTest([]byte(f)) ret = jsn.Fuzz([]byte(f))
} }
} }

View File

@ -1,4 +1,4 @@
package jsn package jsn_test
import ( import (
"bytes" "bytes"
@ -6,6 +6,8 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
"testing" "testing"
"github.com/dosco/super-graph/jsn"
) )
var ( var (
@ -171,13 +173,13 @@ var (
) )
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
values := Get([]byte(input1), [][]byte{ values := jsn.Get([]byte(input1), [][]byte{
[]byte("test_1a"), []byte("test_1a"),
[]byte("__twitter_id"), []byte("__twitter_id"),
[]byte("work_email"), []byte("work_email"),
}) })
expected := []Field{ expected := []jsn.Field{
{[]byte("test_1a"), []byte(`{ "__twitter_id": "ABCD" }`)}, {[]byte("test_1a"), []byte(`{ "__twitter_id": "ABCD" }`)},
{[]byte("__twitter_id"), []byte(`"ABCD"`)}, {[]byte("__twitter_id"), []byte(`"ABCD"`)},
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)}, {[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
@ -214,11 +216,11 @@ func TestGet(t *testing.T) {
} }
func TestGet1(t *testing.T) { func TestGet1(t *testing.T) {
values := Get([]byte(input5), [][]byte{ values := jsn.Get([]byte(input5), [][]byte{
[]byte("thread_slug"), []byte("thread_slug"),
}) })
expected := []Field{ expected := []jsn.Field{
{[]byte("thread_slug"), []byte(`"in-september-2018-slovak-police-stated-that-kuciak-7929"`)}, {[]byte("thread_slug"), []byte(`"in-september-2018-slovak-police-stated-that-kuciak-7929"`)},
} }
@ -238,11 +240,11 @@ func TestGet1(t *testing.T) {
} }
func TestGet2(t *testing.T) { func TestGet2(t *testing.T) {
values := Get([]byte(input6), [][]byte{ values := jsn.Get([]byte(input6), [][]byte{
[]byte("users_cursor"), []byte("threads_cursor"), []byte("users_cursor"), []byte("threads_cursor"),
}) })
expected := []Field{ expected := []jsn.Field{
{[]byte("threads_cursor"), []byte(`null`)}, {[]byte("threads_cursor"), []byte(`null`)},
{[]byte("threads_cursor"), []byte(`25`)}, {[]byte("threads_cursor"), []byte(`25`)},
{[]byte("users_cursor"), []byte(`3`)}, {[]byte("users_cursor"), []byte(`3`)},
@ -264,7 +266,7 @@ func TestGet2(t *testing.T) {
} }
func TestGet3(t *testing.T) { func TestGet3(t *testing.T) {
values := Get(input7, [][]byte{[]byte("data")}) values := jsn.Get(input7, [][]byte{[]byte("data")})
v := values[0].Value v := values[0].Value
if !bytes.Equal(v[len(v)-11:], []byte(`Rangnekar"}`)) { if !bytes.Equal(v[len(v)-11:], []byte(`Rangnekar"}`)) {
@ -277,7 +279,7 @@ func TestGet4(t *testing.T) {
exp = strings.ReplaceAll(exp, "@", "`") exp = strings.ReplaceAll(exp, "@", "`")
values := Get(input8, [][]byte{[]byte("body")}) values := jsn.Get(input8, [][]byte{[]byte("body")})
if string(values[0].Key) != "body" { if string(values[0].Key) != "body" {
t.Fatal("unexpected key") t.Fatal("unexpected key")
@ -291,29 +293,29 @@ func TestGet4(t *testing.T) {
func TestValue(t *testing.T) { func TestValue(t *testing.T) {
v1 := []byte("12345") v1 := []byte("12345")
if !bytes.Equal(Value(v1), v1) { if !bytes.Equal(jsn.Value(v1), v1) {
t.Fatal("Number value invalid") t.Fatal("Number value invalid")
} }
v2 := []byte(`"12345"`) v2 := []byte(`"12345"`)
if !bytes.Equal(Value(v2), []byte(`12345`)) { if !bytes.Equal(jsn.Value(v2), []byte(`12345`)) {
t.Fatal("String value invalid") t.Fatal("String value invalid")
} }
v3 := []byte(`{ "hello": "world" }`) v3 := []byte(`{ "hello": "world" }`)
if Value(v3) != nil { if jsn.Value(v3) != nil {
t.Fatal("Object value is not nil", Value(v3)) t.Fatal("Object value is not nil", jsn.Value(v3))
} }
v4 := []byte(`[ "hello", "world" ]`) v4 := []byte(`[ "hello", "world" ]`)
if Value(v4) != nil { if jsn.Value(v4) != nil {
t.Fatal("List value is not nil") t.Fatal("List value is not nil")
} }
} }
func TestFilter1(t *testing.T) { func TestFilter1(t *testing.T) {
var b bytes.Buffer var b bytes.Buffer
err := Filter(&b, []byte(input2), []string{"id", "full_name", "embed"}) err := jsn.Filter(&b, []byte(input2), []string{"id", "full_name", "embed"})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -329,7 +331,7 @@ func TestFilter2(t *testing.T) {
value := `[{"id":1,"customer_id":"cus_2TbMGf3cl0","object":"charge","amount":100,"amount_refunded":0,"date":"01/01/2019","application":null,"billing_details":{"address":"1 Infinity Drive","zipcode":"94024"}}, {"id":2,"customer_id":"cus_2TbMGf3cl0","object":"charge","amount":150,"amount_refunded":0,"date":"02/18/2019","billing_details":{"address":"1 Infinity Drive","zipcode":"94024"}},{"id":3,"customer_id":"cus_2TbMGf3cl0","object":"charge","amount":150,"amount_refunded":50,"date":"03/21/2019","billing_details":{"address":"1 Infinity Drive","zipcode":"94024"}}]` value := `[{"id":1,"customer_id":"cus_2TbMGf3cl0","object":"charge","amount":100,"amount_refunded":0,"date":"01/01/2019","application":null,"billing_details":{"address":"1 Infinity Drive","zipcode":"94024"}}, {"id":2,"customer_id":"cus_2TbMGf3cl0","object":"charge","amount":150,"amount_refunded":0,"date":"02/18/2019","billing_details":{"address":"1 Infinity Drive","zipcode":"94024"}},{"id":3,"customer_id":"cus_2TbMGf3cl0","object":"charge","amount":150,"amount_refunded":50,"date":"03/21/2019","billing_details":{"address":"1 Infinity Drive","zipcode":"94024"}}]`
var b bytes.Buffer var b bytes.Buffer
err := Filter(&b, []byte(value), []string{"id"}) err := jsn.Filter(&b, []byte(value), []string{"id"})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -343,7 +345,7 @@ func TestFilter2(t *testing.T) {
func TestStrip(t *testing.T) { func TestStrip(t *testing.T) {
path1 := [][]byte{[]byte("data"), []byte("users")} path1 := [][]byte{[]byte("data"), []byte("users")}
value1 := Strip([]byte(input3), path1) value1 := jsn.Strip([]byte(input3), path1)
expected := []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`) expected := []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)
@ -353,7 +355,7 @@ func TestStrip(t *testing.T) {
} }
path2 := [][]byte{[]byte("boo"), []byte("hoo")} path2 := [][]byte{[]byte("boo"), []byte("hoo")}
value2 := Strip([]byte(input3), path2) value2 := jsn.Strip([]byte(input3), path2)
if !bytes.Equal(value2, []byte(input3)) { if !bytes.Equal(value2, []byte(input3)) {
t.Log(value2) t.Log(value2)
@ -364,7 +366,7 @@ func TestStrip(t *testing.T) {
func TestValidateTrue(t *testing.T) { func TestValidateTrue(t *testing.T) {
json := []byte(` [{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`) json := []byte(` [{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)
err := Validate(string(json)) err := jsn.Validate(string(json))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -373,7 +375,7 @@ func TestValidateTrue(t *testing.T) {
func TestValidateFalse(t *testing.T) { func TestValidateFalse(t *testing.T) {
json := []byte(` [{ "hello": 123"<html>}]`) json := []byte(` [{ "hello": 123"<html>}]`)
err := Validate(string(json)) err := jsn.Validate(string(json))
if err == nil { if err == nil {
t.Error("JSON validation failed to detect invalid json") t.Error("JSON validation failed to detect invalid json")
} }
@ -382,12 +384,12 @@ func TestValidateFalse(t *testing.T) {
func TestReplace(t *testing.T) { func TestReplace(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
from := []Field{ from := []jsn.Field{
{[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)}, {[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)},
{[]byte("__twitter_id"), []byte(`"ABC123"`)}, {[]byte("__twitter_id"), []byte(`"ABC123"`)},
} }
to := []Field{ to := []jsn.Field{
{[]byte("__twitter_id"), []byte(`"1234567890"`)}, {[]byte("__twitter_id"), []byte(`"1234567890"`)},
{[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)}, {[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)},
} }
@ -412,7 +414,7 @@ func TestReplace(t *testing.T) {
"__twitter_id":"1234567890" "__twitter_id":"1234567890"
}] }` }] }`
err := Replace(&buf, []byte(input4), from, to) err := jsn.Replace(&buf, []byte(input4), from, to)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -428,7 +430,7 @@ func TestReplaceEmpty(t *testing.T) {
json := `{ "users" : [{"id":1,"full_name":"Sidney St[1]roman","email":"user0@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":2,"full_name":"Jerry Dickinson","email":"user1@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":3,"full_name":"Kenna Cassin","email":"user2@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":4,"full_name":"Mr. Pat Parisian","email":"rodney@kautzer.biz","__users_twitter_id":"2048666903444506956"}, {"id":5,"full_name":"Bette Ebert","email":"janeenrath@goyette.com","__users_twitter_id":"2048666903444506956"}, {"id":6,"full_name":"Everett Kiehn","email":"michael@bartoletti.com","__users_twitter_id":"2048666903444506956"}, {"id":7,"full_name":"Katrina Cronin","email":"loretaklocko@framivolkman.org","__users_twitter_id":"2048666903444506956"}, {"id":8,"full_name":"Caroll Orn Sr.","email":"joannarau@hegmann.io","__users_twitter_id":"2048666903444506956"}, {"id":9,"full_name":"Gwendolyn Ziemann","email":"renaytoy@rutherford.co","__users_twitter_id":"2048666903444506956"}, {"id":10,"full_name":"Mrs. Rosann Fritsch","email":"holliemosciski@thiel.org","__users_twitter_id":"2048666903444506956"}, {"id":11,"full_name":"Arden Koss","email":"cristobalankunding@howewelch.org","__users_twitter_id":"2048666903444506956"}, {"id":12,"full_name":"Brenton Bauch PhD","email":"renee@miller.co","__users_twitter_id":"2048666903444506956"}, {"id":13,"full_name":"Daine Gleichner","email":"andrea@nienow.co","__users_twitter_id":"2048666903444506956"}] }` json := `{ "users" : [{"id":1,"full_name":"Sidney St[1]roman","email":"user0@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":2,"full_name":"Jerry Dickinson","email":"user1@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":3,"full_name":"Kenna Cassin","email":"user2@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":4,"full_name":"Mr. Pat Parisian","email":"rodney@kautzer.biz","__users_twitter_id":"2048666903444506956"}, {"id":5,"full_name":"Bette Ebert","email":"janeenrath@goyette.com","__users_twitter_id":"2048666903444506956"}, {"id":6,"full_name":"Everett Kiehn","email":"michael@bartoletti.com","__users_twitter_id":"2048666903444506956"}, {"id":7,"full_name":"Katrina Cronin","email":"loretaklocko@framivolkman.org","__users_twitter_id":"2048666903444506956"}, {"id":8,"full_name":"Caroll Orn Sr.","email":"joannarau@hegmann.io","__users_twitter_id":"2048666903444506956"}, {"id":9,"full_name":"Gwendolyn Ziemann","email":"renaytoy@rutherford.co","__users_twitter_id":"2048666903444506956"}, {"id":10,"full_name":"Mrs. Rosann Fritsch","email":"holliemosciski@thiel.org","__users_twitter_id":"2048666903444506956"}, {"id":11,"full_name":"Arden Koss","email":"cristobalankunding@howewelch.org","__users_twitter_id":"2048666903444506956"}, {"id":12,"full_name":"Brenton Bauch PhD","email":"renee@miller.co","__users_twitter_id":"2048666903444506956"}, {"id":13,"full_name":"Daine Gleichner","email":"andrea@nienow.co","__users_twitter_id":"2048666903444506956"}] }`
err := Replace(&buf, []byte(json), []Field{}, []Field{}) err := jsn.Replace(&buf, []byte(json), []jsn.Field{}, []jsn.Field{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -442,7 +444,7 @@ func TestReplaceEmpty(t *testing.T) {
func TestKeys1(t *testing.T) { func TestKeys1(t *testing.T) {
json := `[{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]},{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]},{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]}]` json := `[{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]},{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]},{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]}]`
fields := Keys([]byte(json)) fields := jsn.Keys([]byte(json))
exp := []string{ exp := []string{
"id", "posts", "title", "description", "full_name", "email", "books", "name", "description", "id", "posts", "title", "description", "full_name", "email", "books", "name", "description",
@ -462,7 +464,7 @@ func TestKeys1(t *testing.T) {
func TestKeys2(t *testing.T) { func TestKeys2(t *testing.T) {
json := `{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]}` json := `{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]}`
fields := Keys([]byte(json)) fields := jsn.Keys([]byte(json))
exp := []string{ exp := []string{
"id", "posts", "title", "description", "full_name", "email", "books", "name", "description", "id", "posts", "title", "description", "full_name", "email", "books", "name", "description",
@ -491,7 +493,7 @@ func TestKeys3(t *testing.T) {
"user": 123 "user": 123
}` }`
fields := Keys([]byte(json)) fields := jsn.Keys([]byte(json))
exp := []string{ exp := []string{
"insert", "created_at", "test_1a", "type1", "type2", "name", "updated_at", "description", "insert", "created_at", "test_1a", "type1", "type2", "name", "updated_at", "description",
@ -526,7 +528,7 @@ func TestClear(t *testing.T) {
expected := `{"insert":{"created_at":"","test_1a":{"type1":"","type2":[{"a":0.0}]},"name":"","updated_at":"","description":""},"user":0.0,"tags":[]}` expected := `{"insert":{"created_at":"","test_1a":{"type1":"","type2":[{"a":0.0}]},"name":"","updated_at":"","description":""},"user":0.0,"tags":[]}`
err := Clear(&buf, []byte(json)) err := jsn.Clear(&buf, []byte(json))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -541,7 +543,7 @@ func BenchmarkGet(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
Get([]byte(input1), [][]byte{[]byte("__twitter_id")}) jsn.Get([]byte(input1), [][]byte{[]byte("__twitter_id")})
} }
} }
@ -553,7 +555,7 @@ func BenchmarkFilter(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
err := Filter(&buf, []byte(input2), keys) err := jsn.Filter(&buf, []byte(input2), keys)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -566,19 +568,19 @@ func BenchmarkStrip(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
Strip([]byte(input3), path) jsn.Strip([]byte(input3), path)
} }
} }
func BenchmarkReplace(b *testing.B) { func BenchmarkReplace(b *testing.B) {
var buf bytes.Buffer var buf bytes.Buffer
from := []Field{ from := []jsn.Field{
{[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)}, {[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)},
{[]byte("__twitter_id"), []byte(`"ABC123"`)}, {[]byte("__twitter_id"), []byte(`"ABC123"`)},
} }
to := []Field{ to := []jsn.Field{
{[]byte("__twitter_id"), []byte(`"1234567890"`)}, {[]byte("__twitter_id"), []byte(`"1234567890"`)},
{[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)}, {[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)},
} }
@ -587,7 +589,7 @@ func BenchmarkReplace(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
err := Replace(&buf, []byte(input4), from, to) err := jsn.Replace(&buf, []byte(input4), from, to)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View File

@ -133,9 +133,18 @@ func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
if e != 0 { if e != 0 {
e++ e++
if e <= s {
return errors.New("invalid json")
}
if _, err := h.Write(b[s:e]); err != nil { if _, err := h.Write(b[s:e]); err != nil {
return err return err
} }
if (we + 1) <= ws {
return errors.New("invalid json")
}
n, ok := tmap[h.Sum64()] n, ok := tmap[h.Sum64()]
h.Reset() h.Reset()

View File

@ -1,37 +0,0 @@
package jsn
import (
"bytes"
"errors"
)
func unifiedTest(data []byte) error {
err1 := Validate(string(data))
var b1 bytes.Buffer
err2 := Filter(&b1, data, []string{"id", "full_name", "embed"})
path1 := [][]byte{[]byte("data"), []byte("users")}
Strip(data, path1)
from := []Field{
{[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)},
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
}
to := []Field{
{[]byte("__twitter_id"), []byte(`"1234567890"`)},
{[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)},
}
var b2 bytes.Buffer
err3 := Replace(&b2, data, from, to)
Keys(data)
if err1 != nil || err2 != nil || err3 != nil {
return errors.New("there was an error")
}
return nil
}