cli: fix remote schema formatting errors in metadata

closes https://github.com/hasura/graphql-engine/issues/7608
closes https://github.com/hasura/graphql-engine/issues/7459

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3951
GitOrigin-RevId: bad3505c79fab10453580a6a43ad7e71bc2d3753
This commit is contained in:
Aravind K P 2022-03-31 13:57:00 +05:30 committed by hasura-bot
parent 0baef156df
commit 649ef41e3c
10 changed files with 1538 additions and 57 deletions

View File

@ -4,6 +4,8 @@
### Bug fixes and improvements
- cli: fix remote schema metadata formatting issues (#7608)
## v2.5.0-beta.1
### Remote relationships from remote schemas

View File

@ -49,6 +49,7 @@ require (
github.com/spf13/viper v1.10.0
github.com/stretchr/testify v1.7.0
github.com/subosito/gotenv v1.2.0
github.com/vektah/gqlparser v1.3.1
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/src-d/go-git.v4 v4.13.1

View File

@ -83,6 +83,8 @@ github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdc
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg=
github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/ahmetb/go-linq v3.0.0+incompatible h1:qQkjjOXKrKOTy83X8OpRmnKflXKQIL/mC/gMVVDMhOA=
github.com/ahmetb/go-linq v3.0.0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
@ -91,6 +93,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@ -666,6 +670,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@ -933,6 +939,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=

View File

@ -9,20 +9,12 @@ import (
"github.com/hasura/graphql-engine/cli/v2"
"github.com/hasura/graphql-engine/cli/v2/internal/metadataobject"
"github.com/sirupsen/logrus"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
"github.com/vektah/gqlparser/formatter"
"gopkg.in/yaml.v3"
)
type RemoteSchema struct {
Name string `yaml:"name"`
Definition interface{} `yaml:"definition"`
Comment interface{} `yaml:"comment"`
Permission interface{} `yaml:"permissions"`
}
func (r RemoteSchema) BaseDirectory() string {
panic("implement me")
}
type SchemaDefinition struct {
Schema string `yaml:"schema"`
}
@ -75,8 +67,66 @@ func (r *RemoteSchemaConfig) Build() (map[string]interface{}, metadataobject.Err
return map[string]interface{}{r.Key(): obj}, nil
}
type remoteSchema struct {
Name yaml.Node `yaml:"name,omitempty"`
Defintion yaml.Node `yaml:"definition,omitempty"`
Comment yaml.Node `yaml:"comment,omitempty"`
Permissions []permission `yaml:"permissions,omitempty"`
}
type permission struct {
Role yaml.Node `yaml:"role,omitempty"`
Definition definition `yaml:"definition,omitempty"`
}
type definition struct {
Schema string `yaml:"schema,omitempty"`
}
func (r *RemoteSchemaConfig) Export(metadata map[string]yaml.Node) (map[string][]byte, metadataobject.ErrParsingMetadataObject) {
return metadataobject.DefaultExport(r, metadata, r.error, metadataobject.DefaultObjectTypeSequence)
var value interface{}
if v, ok := metadata[r.Key()]; !ok {
value = []yaml.Node{}
} else {
remoteSchemas := []remoteSchema{}
bs, err := yaml.Marshal(v)
if err != nil {
return nil, r.error(err)
}
if err := yaml.Unmarshal(bs, &remoteSchemas); err != nil {
return nil, r.error(err)
}
for rsIdx := range remoteSchemas {
for pIdx := range remoteSchemas[rsIdx].Permissions {
buf := new(bytes.Buffer)
gqlFormatter := formatter.NewFormatter(buf)
schema, err := gqlparser.LoadSchema(&ast.Source{
Input: remoteSchemas[rsIdx].Permissions[pIdx].Definition.Schema,
})
if err != nil {
r.logger.Infof("formatting permission for role %v in remote schema %v failed", remoteSchemas[rsIdx].Permissions[pIdx].Role, remoteSchemas[rsIdx].Name)
r.logger.Debugf("loading schema failed for role: %v remote schema: %v error: %v", remoteSchemas[rsIdx].Permissions[pIdx].Role, remoteSchemas[rsIdx].Name, err)
continue
}
gqlFormatter.FormatSchema(schema)
if buf.Len() > 0 {
remoteSchemas[rsIdx].Permissions[pIdx].Definition.Schema = buf.String()
}
}
}
value = remoteSchemas
}
var buf bytes.Buffer
err := metadataobject.GetEncoder(&buf).Encode(value)
if err != nil {
return nil, r.error(err)
}
return map[string][]byte{
filepath.ToSlash(filepath.Join(r.BaseDirectory(), r.Filename())): buf.Bytes(),
}, nil
}
func (r *RemoteSchemaConfig) GetFiles() ([]string, metadataobject.ErrParsingMetadataObject) {

View File

@ -43,6 +43,16 @@ func TestRemoteSchemaConfig_Build(t *testing.T) {
"testdata/build_test/t2/want.golden.json",
false,
},
{
"t3",
"can build metadata json with multiline strings",
fields{
MetadataDir: "testdata/build_test/t3/metadata",
logger: logrus.New(),
},
"testdata/build_test/t3/want.golden.json",
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -61,7 +71,7 @@ func TestRemoteSchemaConfig_Build(t *testing.T) {
assert.NoError(t, err)
// uncomment following lines to update golden file
//assert.NoError(t, ioutil.WriteFile(tt.wantGolden, jsonbs, os.ModePerm))
// assert.NoError(t, ioutil.WriteFile(tt.wantGolden, jsonbs, os.ModePerm))
wantbs, err := ioutil.ReadFile(tt.wantGolden)
assert.NoError(t, err)
@ -170,6 +180,33 @@ func TestRemoteSchemaConfig_Export(t *testing.T) {
},
false,
},
{
"t4",
"can export remote schema with multiline strings - 2",
fields{
MetadataDir: "metadata",
logger: logrus.New(),
},
args{
metadata: func() map[string]yaml.Node {
bs, err := ioutil.ReadFile("testdata/export_test/t4/metadata.json")
assert.NoError(t, err)
yamlbs, err := metadatautil.JSONToYAML(bs)
assert.NoError(t, err)
var v map[string]yaml.Node
assert.NoError(t, yaml.Unmarshal(yamlbs, &v))
return v
}(),
},
map[string][]byte{
"metadata/remote_schemas.yaml": func() []byte {
bs, err := ioutil.ReadFile("testdata/export_test/t4/want.remote_schemas.yaml")
assert.NoError(t, err)
return bs
}(),
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -181,6 +218,7 @@ func TestRemoteSchemaConfig_Export(t *testing.T) {
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
for k, v := range got {
assert.Contains(t, tt.want, k)
// uncomment to update golden files

View File

@ -0,0 +1,57 @@
- name: countries
comment: foo countries
permissions:
- role: user
definition:
schema: |-
schema { query: Query }
type Continent { code: ID!
countries: [Country!]!
name: String!
}
type Country { capital: String
code: ID!
continent: Continent!
currency: String
emoji: String!
emojiU: String!
languages: [Language!]!
name: String!
native: String!
phone: String!
states: [State!]!
}
type Language { code: ID!
name: String
native: String
rtl: Boolean!
}
type Query { continent(code: ID!): Continent
countries(filter: CountryFilterInput): [Country!]!
languages(filter: LanguageFilterInput): [Language!]!
}
type State { code: String
country: Country!
name: String!
}
input CountryFilterInput {code: StringQueryOperatorInput
continent: StringQueryOperatorInput
currency: StringQueryOperatorInput
}
input LanguageFilterInput {code: StringQueryOperatorInput
}
input StringQueryOperatorInput {eq: String
glob: String
in: [String]
ne: String
nin: [String]
regex: String
}

View File

@ -0,0 +1 @@
{"remote_schemas": [{"name": "countries", "comment": "foo countries", "permissions": [{"role": "user", "definition": {"schema": "schema { query: Query }\n\ntype Continent { code: ID!\n countries: [Country!]!\n name: String!\n}\n\ntype Country { capital: String\n code: ID!\n continent: Continent!\n currency: String\n emoji: String!\n emojiU: String!\n languages: [Language!]!\n name: String!\n native: String!\n phone: String!\n states: [State!]!\n}\n\ntype Language { code: ID!\n name: String\n native: String\n rtl: Boolean!\n}\n\ntype Query { continent(code: ID!): Continent\n countries(filter: CountryFilterInput): [Country!]!\n languages(filter: LanguageFilterInput): [Language!]!\n}\n\ntype State { code: String\n country: Country!\n name: String!\n}\n\ninput CountryFilterInput {code: StringQueryOperatorInput\n continent: StringQueryOperatorInput\n currency: StringQueryOperatorInput\n}\n\ninput LanguageFilterInput {code: StringQueryOperatorInput\n}\n\ninput StringQueryOperatorInput {eq: String\n glob: String\n in: [String]\n ne: String\n nin: [String]\n regex: String\n}"}}]}]}

View File

@ -10,55 +10,54 @@
permissions:
- role: user
definition:
schema: |-
schema { query: Query }
type Continent { code: ID!
countries: [Country!]!
name: String!
schema: |
type Continent {
code: ID!
countries: [Country!]!
name: String!
}
type Country { capital: String
code: ID!
continent: Continent!
currency: String
emoji: String!
emojiU: String!
languages: [Language!]!
name: String!
native: String!
phone: String!
states: [State!]!
type Country {
capital: String
code: ID!
continent: Continent!
currency: String
emoji: String!
emojiU: String!
languages: [Language!]!
name: String!
native: String!
phone: String!
states: [State!]!
}
type Language { code: ID!
name: String
native: String
rtl: Boolean!
input CountryFilterInput {
code: StringQueryOperatorInput
continent: StringQueryOperatorInput
currency: StringQueryOperatorInput
}
type Query { continent(code: ID!): Continent
countries(filter: CountryFilterInput): [Country!]!
languages(filter: LanguageFilterInput): [Language!]!
type Language {
code: ID!
name: String
native: String
rtl: Boolean!
}
type State { code: String
country: Country!
name: String!
input LanguageFilterInput {
code: StringQueryOperatorInput
}
input CountryFilterInput {code: StringQueryOperatorInput
continent: StringQueryOperatorInput
currency: StringQueryOperatorInput
type Query {
continent(code: ID!): Continent
countries(filter: CountryFilterInput): [Country!]!
languages(filter: LanguageFilterInput): [Language!]!
}
input LanguageFilterInput {code: StringQueryOperatorInput
type State {
code: String
country: Country!
name: String!
}
input StringQueryOperatorInput {eq: String
glob: String
in: [String]
ne: String
nin: [String]
regex: String
input StringQueryOperatorInput {
eq: String
glob: String
in: [String]
ne: String
nin: [String]
regex: String
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff