diff --git a/codegen/client.go b/codegen/client.go new file mode 100644 index 000000000..120dd709d --- /dev/null +++ b/codegen/client.go @@ -0,0 +1,309 @@ +// Copyright (c) 2018 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package codegen + +import ( + "path/filepath" + + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" +) + +// ClientConfigBase is the struct for client-config.yaml. It is the base for +// specific type of client. +type ClientConfigBase struct { + Name string `yaml:"name" json:"name"` + Type string `yaml:"type" json:"type"` + Dependencies Dependencies `yaml:"dependencies" json:"dependencies"` +} + +type clientConfig interface { + NewClientSpec( + instance *ModuleInstance, + h *PackageHelper) (*ClientSpec, error) + getCustomImportPath() string + getFixture() *Fixture +} + +// ClientSubConfig is for http client and tchannel client +type ClientSubConfig struct { + ExposedMethods map[string]string `yaml:"exposedMethods" json:"exposedMethods"` + CustomImportPath string `yaml:"customImportPath" json:"customImportPath"` + ThriftFile string `yaml:"thriftFile" json:"thriftFile"` + ThriftFileSha string `yaml:"thriftFileSha" json:"thriftFileSha"` + SidecarRouter string `yaml:"sidecarRouter" json:"sidecarRouter"` + Fixture *Fixture `yaml:"fixture" json:"fixture"` +} + +func validateExposedMethods(methods map[string]string) error { + // Check duplication + visited := make(map[string]string, len(methods)) + for key, val := range methods { + if _, ok := visited[val]; ok { + return errors.Errorf( + "value %q of the exposedMethods is not unique", val, + ) + } + visited[val] = key + } + return nil +} + +func (c *ClientSubConfig) validate() error { + if c.ThriftFile == "" { + return errors.Errorf( + "Client config must have \"thriftFile\" field") + } + if c.ThriftFileSha == "" { + return errors.Errorf( + "Client config must have \"thriftFileSha\" field") + } + return nil +} + +// HTTPClientConfig represents the config for a HTTP client. This is a dirivative +// of the ClientConfigBase +type HTTPClientConfig struct { + ClientConfigBase `yaml:",inline" json:",inline"` + Config *ClientSubConfig `yaml:"config" json:"config"` +} + +func newHTTPClientConfig(raw []byte) (*HTTPClientConfig, error) { + config := &HTTPClientConfig{} + if errUnmarshal := yaml.Unmarshal(raw, config); errUnmarshal != nil { + return nil, errors.Wrap( + errUnmarshal, "Could not parse HTTP client config data") + } + + if config.Type != "http" { + return nil, errors.Errorf("Client type (%q) is not \"http\"", config.Type) + } + + if config.Config == nil { + return nil, errors.Errorf("HTTP client config field missing") + } + + if errValidation := config.Config.validate(); errValidation != nil { + return nil, errors.Wrap( + errValidation, "HTTP client config data validation failed") + } + + if errMethods := validateExposedMethods(config.Config.ExposedMethods); errMethods != nil { + return nil, errors.Wrap( + errMethods, "HTTP client exposed mehods validation failed") + } + + return config, nil +} + +func newClientSpecHelper( + clientType string, + config *ClientSubConfig, + instance *ModuleInstance, + h *PackageHelper, + annotate bool, +) (*ClientSpec, error) { + thriftFile := filepath.Join(h.ThriftIDLPath(), config.ThriftFile) + mspec, err := NewModuleSpec(thriftFile, annotate, false, h) + + if err != nil { + return nil, errors.Wrapf( + err, "Could not build module spec for thrift %s: ", thriftFile, + ) + } + mspec.PackageName = mspec.PackageName + "client" + + cspec := &ClientSpec{ + ModuleSpec: mspec, + YAMLFile: instance.YAMLFileName, + JSONFile: instance.JSONFileName, + ClientType: clientType, + ImportPackagePath: instance.PackageInfo.ImportPackagePath(), + ImportPackageAlias: instance.PackageInfo.ImportPackageAlias(), + ExportName: instance.PackageInfo.ExportName, + ExportType: instance.PackageInfo.ExportType, + ThriftFile: thriftFile, + ClientID: instance.InstanceName, + ClientName: instance.PackageInfo.QualifiedInstanceName, + ExposedMethods: config.ExposedMethods, + SidecarRouter: config.SidecarRouter, + } + + return cspec, nil +} + +// NewClientSpec creates a client spec from a client module instance +func (c *HTTPClientConfig) NewClientSpec( + instance *ModuleInstance, + h *PackageHelper) (*ClientSpec, error) { + return newClientSpecHelper(c.Type, c.Config, instance, h, true) +} + +func (c *HTTPClientConfig) getCustomImportPath() string { + return c.Config.CustomImportPath +} + +func (c *HTTPClientConfig) getFixture() *Fixture { + return c.Config.Fixture +} + +// TChannelClientConfig represents the config for a TChannel client. This is a +// dirivative of the ClientConfigBase +type TChannelClientConfig struct { + ClientConfigBase `yaml:",inline" json:",inline"` + Config *ClientSubConfig `yaml:"config" json:"config"` +} + +func newTChannelClientConfig(raw []byte) (*TChannelClientConfig, error) { + config := &TChannelClientConfig{} + if errUnmarshal := yaml.Unmarshal(raw, config); errUnmarshal != nil { + return nil, errors.Wrap( + errUnmarshal, "Could not parse TChannel client config data") + } + + if config.Type != "tchannel" { + return nil, errors.Errorf("Client type (%q) is not \"tchannel\"", config.Type) + } + + if config.Config == nil { + return nil, errors.Errorf("TChannel client config field missing") + } + + if errValidation := config.Config.validate(); errValidation != nil { + return nil, errors.Wrap( + errValidation, "TChannel client config data validation failed") + } + + if errMethods := validateExposedMethods(config.Config.ExposedMethods); errMethods != nil { + return nil, errors.Wrap( + errMethods, "TChannel client exposed mehods validation failed") + } + + return config, nil +} + +// NewClientSpec creates a client spec from a client module instance +func (c *TChannelClientConfig) NewClientSpec( + instance *ModuleInstance, + h *PackageHelper) (*ClientSpec, error) { + return newClientSpecHelper(c.Type, c.Config, instance, h, false) +} + +func (c *TChannelClientConfig) getCustomImportPath() string { + return c.Config.CustomImportPath +} + +func (c *TChannelClientConfig) getFixture() *Fixture { + return c.Config.Fixture +} + +// CustomClientSubConfig is for custom client +type CustomClientSubConfig struct { + CustomImportPath string `yaml:"customImportPath" json:"customImportPath"` + Fixture *Fixture `yaml:"fixture" json:"fixture"` +} + +// CustomClientConfig represents the config for a custom client. This is a +// dirivative of the ClientConfigBase +type CustomClientConfig struct { + ClientConfigBase `yaml:",inline" json:",inline"` + Config *CustomClientSubConfig `yaml:"config" json:"config"` +} + +func newCustomClientConfig(raw []byte) (*CustomClientConfig, error) { + config := &CustomClientConfig{} + if errUnmarshal := yaml.Unmarshal(raw, config); errUnmarshal != nil { + return nil, errors.Wrap( + errUnmarshal, "Could not parse Custom client config data") + } + + if config.Type != "custom" { + return nil, errors.Errorf("Client type (%q) is not \"custom\"", config.Type) + } + if config.Config == nil { + return nil, errors.Errorf("Custom client config field missing") + } + + if config.Config.CustomImportPath == "" { + return nil, errors.Errorf( + "Custom client config must have \"CustomImportPath\" field") + } + + return config, nil +} + +// NewClientSpec creates a client spec from a http client module instance +func (c *CustomClientConfig) NewClientSpec( + instance *ModuleInstance, + h *PackageHelper) (*ClientSpec, error) { + + spec := &ClientSpec{ + YAMLFile: instance.YAMLFileName, + JSONFile: instance.JSONFileName, + ImportPackagePath: instance.PackageInfo.ImportPackagePath(), + ImportPackageAlias: instance.PackageInfo.ImportPackageAlias(), + ExportName: instance.PackageInfo.ExportName, + ExportType: instance.PackageInfo.ExportType, + ClientType: c.Type, + ClientID: c.Name, + ClientName: instance.PackageInfo.QualifiedInstanceName, + CustomImportPath: c.Config.CustomImportPath, + } + + return spec, nil +} + +func (c *CustomClientConfig) getCustomImportPath() string { + return c.Config.CustomImportPath +} + +func (c *CustomClientConfig) getFixture() *Fixture { + return c.Config.Fixture +} + +func getClientType(raw []byte) (string, error) { + clientConfig := ClientConfigBase{} + if err := yaml.Unmarshal(raw, &clientConfig); err != nil { + return "UNKNOWN", errors.Wrap( + err, "Could not parse client config data to determine client type") + } + return clientConfig.Type, nil +} + +func newClientConfig(raw []byte) (clientConfig, error) { + clientType, errType := getClientType(raw) + if errType != nil { + return nil, errors.Wrap( + errType, "Could not determine client type") + } + + switch clientType { + case "http": + return newHTTPClientConfig(raw) + case "tchannel": + return newTChannelClientConfig(raw) + case "custom": + return newCustomClientConfig(raw) + default: + return nil, errors.Errorf( + "Unknown client type %q", clientType) + } +} diff --git a/codegen/client_test.go b/codegen/client_test.go new file mode 100644 index 000000000..7eade0c4a --- /dev/null +++ b/codegen/client_test.go @@ -0,0 +1,601 @@ +// Copyright (c) 2018 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package codegen + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + httpClientYAML = ` +name: test +type: http +dependencies: + client: + - a + - b +config: + thriftFileSha: thriftFileSha + thriftFile: clients/bar/bar.thrift + customImportPath: path + sidecarRouter: sidecar + fixture: + importPath: import + scenarios: + scenario: + - s1 + - s2 + exposedMethods: + a: method +` + + tchannelClientYAML = ` +name: test +type: tchannel +dependencies: + client: + - a + - b +config: + thriftFileSha: thriftFileSha + thriftFile: clients/bar/bar.thrift + customImportPath: path + sidecarRouter: sidecar + fixture: + importPath: import + scenarios: + scenario: + - s1 + - s2 + exposedMethods: + a: method +` + + customClientYAML = ` +name: test +type: custom +dependencies: + client: + - a + - b +config: + customImportPath: path + fixture: + importPath: import + scenarios: + scenario: + - s1 + - s2 +` +) + +func TestValidationDetectsThriftFileMissing(t *testing.T) { + c := &ClientSubConfig{ + ExposedMethods: map[string]string{}, + CustomImportPath: "CustomImportPat", + ThriftFile: "", // missing + ThriftFileSha: "ThriftFile", + SidecarRouter: "", + Fixture: nil, + } + expectedErr := "Client config must have \"thriftFile\" field" + assert.Equal(t, expectedErr, c.validate().Error()) +} + +func TestValidationDetectsThriftShaMissing(t *testing.T) { + c := &ClientSubConfig{ + ExposedMethods: map[string]string{}, + CustomImportPath: "CustomImportPat", + ThriftFile: "ThriftFile", + ThriftFileSha: "", // missing + SidecarRouter: "", + Fixture: nil, + } + expectedErr := "Client config must have \"thriftFileSha\" field" + assert.Equal(t, expectedErr, c.validate().Error()) +} + +func TestDuplicatedExposedMethods(t *testing.T) { + + duplication := map[string]string{ + "a": "m", + "b": "m", + } + err := validateExposedMethods(duplication) + expectedErr := "value \"m\" of the exposedMethods is not unique" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewHTTPClientConfigUnmarshalFilure(t *testing.T) { + invalidYAML := "{{{" + _, err := newHTTPClientConfig([]byte(invalidYAML)) + expectedErr := "Could not parse HTTP client config data: yaml: line 1: did not find expected node content" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewTChannelClientConfigUnmarshalFilure(t *testing.T) { + invalidYAML := "{{{" + _, err := newTChannelClientConfig([]byte(invalidYAML)) + expectedErr := "Could not parse TChannel client config data: yaml: line 1: did not find expected node content" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewCustomClientConfigUnmarshalFilure(t *testing.T) { + invalidYAML := "{{{" + _, err := newCustomClientConfig([]byte(invalidYAML)) + expectedErr := "Could not parse Custom client config data: yaml: line 1: did not find expected node content" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewHTTPClientConfigSubConfigMissing(t *testing.T) { + configYAML := ` +name: test +type: http +# config is missing +` + _, err := newHTTPClientConfig([]byte(configYAML)) + expectedErr := "HTTP client config field missing" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewTChannelClientConfigSubConfigMissing(t *testing.T) { + configYAML := ` +name: test +type: tchannel +# config is missing +` + _, err := newTChannelClientConfig([]byte(configYAML)) + expectedErr := "TChannel client config field missing" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewCustomClientConfigSubConfigMissing(t *testing.T) { + configYAML := ` +name: test +type: custom +# config is missing +` + _, err := newCustomClientConfig([]byte(configYAML)) + expectedErr := "Custom client config field missing" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewHTTPClientConfigTypeError(t *testing.T) { + configYAML := ` +name: test +type: some_type +config: + thriftFile: thriftFile + thriftFileSha: thriftFileSha + exposedMethods: + a: method_a +` + _, err := newHTTPClientConfig([]byte(configYAML)) + expectedErr := "Client type (\"some_type\") is not \"http\"" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewTChannelClientConfigTypeError(t *testing.T) { + configYAML := ` +name: test +type: some_type +config: + thriftFile: thriftFile + thriftFileSha: thriftFileSha + exposedMethods: + a: method_a +` + _, err := newTChannelClientConfig([]byte(configYAML)) + expectedErr := "Client type (\"some_type\") is not \"tchannel\"" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewCustomClientConfigTypeError(t *testing.T) { + configYAML := ` +name: test +type: some_type +config: + thriftFile: thriftFile + thriftFileSha: thriftFileSha + exposedMethods: + a: method_a +` + _, err := newCustomClientConfig([]byte(configYAML)) + expectedErr := "Client type (\"some_type\") is not \"custom\"" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewHTTPClientConfigValidationFailure(t *testing.T) { + configYAML := ` +name: test +type: http +config: + thriftFileSha: thriftFileSha + # thriftFile is missing +` + _, err := newHTTPClientConfig([]byte(configYAML)) + expectedErr := "HTTP client config data validation failed: Client config must have \"thriftFile\" field" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewTChannelClientConfigValidationFailure(t *testing.T) { + configYAML := ` +name: test +type: tchannel +config: + thriftFileSha: thriftFileSha + # thriftFile is missing +` + _, err := newTChannelClientConfig([]byte(configYAML)) + expectedErr := "TChannel client config data validation failed: Client config must have \"thriftFile\" field" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewCustomClientConfigValidationFailure(t *testing.T) { + configYAML := ` +name: test +type: custom +config: + thriftFileSha: thriftFileSha + # CustomImportPath is missing +` + _, err := newCustomClientConfig([]byte(configYAML)) + expectedErr := "Custom client config must have \"CustomImportPath\" field" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewHTTPClientConfigDuplicatedMethodsFailure(t *testing.T) { + configYAML := ` +name: test +type: http +config: + thriftFileSha: thriftFileSha + thriftFile: thriftFile + exposedMethods: + a: method + b: method +` + _, err := newHTTPClientConfig([]byte(configYAML)) + expectedErr := "HTTP client exposed mehods validation failed: value \"method\" of the exposedMethods is not unique" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewTChannelClientConfigDuplicatedMethodsFailure(t *testing.T) { + configYAML := ` +name: test +type: tchannel +config: + thriftFileSha: thriftFileSha + thriftFile: thriftFile + exposedMethods: + a: method + b: method +` + _, err := newTChannelClientConfig([]byte(configYAML)) + expectedErr := "TChannel client exposed mehods validation failed: value \"method\" of the exposedMethods is not unique" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestGetConfigTypeFailure(t *testing.T) { + clientType, err := getClientType([]byte("{{{")) + + expectedErr := "Could not parse client config data to determine client type: yaml: line 1: did not find expected node content" + assert.Equal(t, "UNKNOWN", clientType) + assert.Equal(t, expectedErr, err.Error()) +} + +func TestGetConfigTypeSuccess(t *testing.T) { + configYAML := ` +type: client_type +` + clientType, err := getClientType([]byte(configYAML)) + + assert.Equal(t, "client_type", clientType) + assert.NoError(t, err) +} + +func TestNewClientConfigTypeError(t *testing.T) { + configYAML := ` +name: test +type: : # malformated type +config: + thriftFileSha: thriftFileSha +` + + _, err := newClientConfig([]byte(configYAML)) + expectedErr := "Could not determine client type: Could not parse client config data to determine client type: yaml: line 3: mapping values are not allowed in this context" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewClientConfigUnknownClientType(t *testing.T) { + configYAML := ` +name: test +type: unknown +config: + thriftFileSha: thriftFileSha +` + _, err := newClientConfig([]byte(configYAML)) + expectedErr := "Unknown client type \"unknown\"" + assert.Equal(t, expectedErr, err.Error()) +} + +func TestNewClientConfigGetHTTPClient(t *testing.T) { + client, err := newClientConfig([]byte(httpClientYAML)) + expectedClient := HTTPClientConfig{ + ClientConfigBase: ClientConfigBase{ + Name: "test", + Type: "http", + Dependencies: Dependencies{ + Client: []string{"a", "b"}, + }, + }, + Config: &ClientSubConfig{ + ExposedMethods: map[string]string{ + "a": "method", + }, + CustomImportPath: "path", + ThriftFileSha: "thriftFileSha", + ThriftFile: "clients/bar/bar.thrift", + SidecarRouter: "sidecar", + Fixture: &Fixture{ + ImportPath: "import", + Scenarios: map[string][]string{ + "scenario": {"s1", "s2"}, + }, + }, + }, + } + assert.NoError(t, err) + assert.Equal(t, &expectedClient, client) +} + +func TestNewClientConfigGetTChannelClient(t *testing.T) { + client, err := newClientConfig([]byte(tchannelClientYAML)) + expectedClient := TChannelClientConfig{ + ClientConfigBase: ClientConfigBase{ + Name: "test", + Type: "tchannel", + Dependencies: Dependencies{ + Client: []string{"a", "b"}, + }, + }, + Config: &ClientSubConfig{ + ExposedMethods: map[string]string{ + "a": "method", + }, + CustomImportPath: "path", + ThriftFileSha: "thriftFileSha", + ThriftFile: "clients/bar/bar.thrift", + SidecarRouter: "sidecar", + Fixture: &Fixture{ + ImportPath: "import", + Scenarios: map[string][]string{ + "scenario": {"s1", "s2"}, + }, + }, + }, + } + assert.NoError(t, err) + assert.Equal(t, &expectedClient, client) +} + +func TestNewClientConfigGetCustomClient(t *testing.T) { + client, err := newClientConfig([]byte(customClientYAML)) + expectedClient := CustomClientConfig{ + ClientConfigBase: ClientConfigBase{ + Name: "test", + Type: "custom", + Dependencies: Dependencies{ + Client: []string{"a", "b"}, + }, + }, + Config: &CustomClientSubConfig{ + CustomImportPath: "path", + Fixture: &Fixture{ + ImportPath: "import", + Scenarios: map[string][]string{ + "scenario": {"s1", "s2"}, + }, + }, + }, + } + assert.NoError(t, err) + assert.Equal(t, &expectedClient, client) +} + +func testHTTPClientGetCustomImportPath(t *testing.T) { + client, err := newClientConfig([]byte(httpClientYAML)) + assert.NoError(t, err) + assert.Equal(t, "path", client.getCustomImportPath()) +} + +func testTChannelClientGetCustomImportPath(t *testing.T) { + client, err := newClientConfig([]byte(tchannelClientYAML)) + assert.NoError(t, err) + assert.Equal(t, "path", client.getCustomImportPath()) +} + +func testCustomClientGetCustomImportPath(t *testing.T) { + client, err := newClientConfig([]byte(customClientYAML)) + assert.NoError(t, err) + assert.Equal(t, "path", client.getCustomImportPath()) +} + +func testHTTPClientGetFixture(t *testing.T) { + client, err := newClientConfig([]byte(httpClientYAML)) + assert.NoError(t, err) + expectedFixture := &Fixture{ + ImportPath: "import", + Scenarios: map[string][]string{ + "scenario": {"s1", "s2"}, + }, + } + assert.Equal(t, expectedFixture, client.getFixture()) +} + +func testTChannelClientGetFixture(t *testing.T) { + client, err := newClientConfig([]byte(tchannelClientYAML)) + assert.NoError(t, err) + expectedFixture := &Fixture{ + ImportPath: "import", + Scenarios: map[string][]string{ + "scenario": {"s1", "s2"}, + }, + } + assert.Equal(t, expectedFixture, client.getFixture()) +} + +func testCustomClientGetFixture(t *testing.T) { + client, err := newClientConfig([]byte(customClientYAML)) + assert.NoError(t, err) + expectedFixture := &Fixture{ + ImportPath: "import", + Scenarios: map[string][]string{ + "scenario": {"s1", "s2"}, + }, + } + assert.Equal(t, expectedFixture, client.getFixture()) +} + +func newTestPackageHelper(t *testing.T) *PackageHelper { + relativeGatewayPath := "../examples/example-gateway" + absGatewayPath, err := filepath.Abs(relativeGatewayPath) + assert.NoError(t, err, "failed to get abs path %s", relativeGatewayPath) + + packageRoot := "github.com/uber/zanzibar/examples/example-gateway" + options := &PackageHelperOptions{ + RelTargetGenDir: "tmpDir", + CopyrightHeader: "copyright", + GenCodePackage: packageRoot + "/build/gen-code", + TraceKey: "trace-key", + } + + h, err := NewPackageHelper( + packageRoot, + absGatewayPath, + options, + ) + assert.NoError(t, err, "failed to create package helper") + return h + +} + +func TestHTTPClientNewClientSpecFailedWithThriftCompilation(t *testing.T) { + configYAML := ` +name: test +type: http +config: + thriftFileSha: thriftFileSha + thriftFile: NOT_EXIST + exposedMethods: + a: method +` + client, errClient := newClientConfig([]byte(configYAML)) + assert.NoError(t, errClient) + + h := newTestPackageHelper(t) + _, errSpec := client.NewClientSpec(nil /* ModuleInstance */, h) + assert.Error(t, errSpec) +} + +// only for http and tchannel clients +func doNewClientSpecTest(t *testing.T, rawConfig []byte, clientType string) { + client, errClient := newClientConfig(rawConfig) + assert.NoError(t, errClient) + instance := &ModuleInstance{ + YAMLFileName: "YAMLFileName", + JSONFileName: "JSONFileName", + InstanceName: "InstanceName", + PackageInfo: &PackageInfo{ + ExportName: "ExportName", + ExportType: "ExportType", + QualifiedInstanceName: "QualifiedInstanceName", + }, + } + h := newTestPackageHelper(t) + + thriftFile := filepath.Join(h.ThriftIDLPath(), "clients/bar/bar.thrift") + expectedSpec := &ClientSpec{ + ModuleSpec: nil, + YAMLFile: instance.YAMLFileName, + JSONFile: instance.JSONFileName, + ClientType: clientType, + ImportPackagePath: instance.PackageInfo.ImportPackagePath(), + ImportPackageAlias: instance.PackageInfo.ImportPackageAlias(), + ExportName: instance.PackageInfo.ExportName, + ExportType: instance.PackageInfo.ExportType, + ThriftFile: thriftFile, + ClientID: instance.InstanceName, + ClientName: instance.PackageInfo.QualifiedInstanceName, + ExposedMethods: map[string]string{ + "a": "method", + }, + SidecarRouter: "sidecar", + } + + spec, errSpec := client.NewClientSpec(instance, h) + spec.ModuleSpec = nil // Not interested in ModuleSpec here + assert.NoError(t, errSpec) + assert.Equal(t, expectedSpec, spec) +} + +func TestHTTPClientNewClientSpec(t *testing.T) { + doNewClientSpecTest(t, []byte(httpClientYAML), "http") +} + +func TestTChannelClientNewClientSpec(t *testing.T) { + doNewClientSpecTest(t, []byte(tchannelClientYAML), "tchannel") +} + +func TestCustomClientNewClientSpec(t *testing.T) { + client, errClient := newClientConfig([]byte(customClientYAML)) + assert.NoError(t, errClient) + instance := &ModuleInstance{ + YAMLFileName: "YAMLFileName", + JSONFileName: "JSONFileName", + InstanceName: "InstanceName", + PackageInfo: &PackageInfo{ + ExportName: "ExportName", + ExportType: "ExportType", + QualifiedInstanceName: "QualifiedInstanceName", + }, + } + + expectedSpec := &ClientSpec{ + YAMLFile: instance.YAMLFileName, + JSONFile: instance.JSONFileName, + ImportPackagePath: instance.PackageInfo.ImportPackagePath(), + ImportPackageAlias: instance.PackageInfo.ImportPackageAlias(), + ExportName: instance.PackageInfo.ExportName, + ExportType: instance.PackageInfo.ExportType, + ClientType: "custom", + ClientID: "test", + ClientName: instance.PackageInfo.QualifiedInstanceName, + CustomImportPath: "path", + } + + spec, errSpec := client.NewClientSpec(instance, nil) + assert.NoError(t, errSpec) + assert.Equal(t, expectedSpec, spec) +} diff --git a/codegen/gateway.go b/codegen/gateway.go index 4a90d0ae5..baba0d223 100644 --- a/codegen/gateway.go +++ b/codegen/gateway.go @@ -47,10 +47,15 @@ const ( resHeaders = "resHeaderMap" ) +// Deprecated +// Add validation to the constructor of the configs var mandatoryClientFields = []string{ "thriftFile", "thriftFileSha", } + +// Deprecated +// Add validation to the constructor of the config var mandatoryCustomClientFields = []string{ "customImportPath", } @@ -116,6 +121,9 @@ type ModuleClassConfig struct { // ClientClassConfig represents the specific config for // a client. This is a downcast of the moduleClassConfig. +// +// Deprecated +// type ClientClassConfig struct { Name string `yaml:"name" json:"name"` Type string `yaml:"type" json:"type"` @@ -192,32 +200,21 @@ func NewClientSpec( instance *ModuleInstance, h *PackageHelper, ) (*ClientSpec, error) { - clientConfig := &ClientClassConfig{} - - if err := yaml.Unmarshal(instance.YAMLFileRaw, &clientConfig); err != nil { + clientConfig, errNew := newClientConfig(instance.YAMLFileRaw) + if errNew != nil { return nil, errors.Wrapf( - err, + errNew, "Could not parse class config yaml file: %s", getModuleConfigFileName(instance), ) } - - switch clientConfig.Type { - case "http": - return NewHTTPClientSpec(instance, clientConfig, h) - case "tchannel": - return NewTChannelClientSpec(instance, clientConfig, h) - case "custom": - return NewCustomClientSpec(instance, clientConfig, h) - default: - return nil, errors.Errorf( - "Cannot support unknown clientType for client %q", - getModuleConfigFileName(instance), - ) - } + return clientConfig.NewClientSpec(instance, h) } // NewHTTPClientSpec creates a client spec from a http client module instance +// +// Deprecated +// func NewHTTPClientSpec( instance *ModuleInstance, clientConfig *ClientClassConfig, @@ -227,6 +224,9 @@ func NewHTTPClientSpec( } // NewTChannelClientSpec creates a client spec from a yaml file whose type is tchannel +// +// Deprecated +// func NewTChannelClientSpec( instance *ModuleInstance, clientConfig *ClientClassConfig, @@ -236,6 +236,9 @@ func NewTChannelClientSpec( } // NewCustomClientSpec creates a client spec from a yaml file whose type is custom +// +// Deprecated +// func NewCustomClientSpec( instance *ModuleInstance, clientConfig *ClientClassConfig, @@ -267,6 +270,7 @@ func NewCustomClientSpec( return clientSpec, nil } +// Deprecated func getExposedMethods( clientConfig *ClientClassConfig) (map[string]string, error) { rawMethods := clientConfig.Config["exposedMethods"] @@ -318,10 +322,12 @@ func getExposedMethods( return result, nil } +// Deprecated func newClientSpec( instance *ModuleInstance, clientConfig *ClientClassConfig, - wantAnnot bool, h *PackageHelper, + wantAnnot bool, + h *PackageHelper, ) (*ClientSpec, error) { config := clientConfig.Config diff --git a/codegen/module_system.go b/codegen/module_system.go index abc72127a..8394ba5d3 100644 --- a/codegen/module_system.go +++ b/codegen/module_system.go @@ -389,19 +389,6 @@ func NewDefaultModuleSystem( return system, nil } -func readClientConfig(rawConfig []byte) (*ClientClassConfig, error) { - var clientConfig ClientClassConfig - if err := yaml.Unmarshal(rawConfig, &clientConfig); err != nil { - return nil, errors.Wrapf( - err, - "Error reading config for client instance", - ) - } - clientConfig.Config["clientId"] = clientConfig.Name - clientConfig.Config["clientType"] = clientConfig.Type - return &clientConfig, nil -} - func readEndpointConfig(rawConfig []byte) (*EndpointClassConfig, error) { var endpointConfig EndpointClassConfig if err := yaml.Unmarshal(rawConfig, &endpointConfig); err != nil { @@ -428,7 +415,7 @@ func (g *HTTPClientGenerator) ComputeSpec( instance *ModuleInstance, ) (interface{}, error) { // Parse the client config from the endpoint YAML file - clientConfig, err := readClientConfig(instance.YAMLFileRaw) + clientConfig, err := newClientConfig(instance.YAMLFileRaw) if err != nil { return nil, errors.Wrapf( err, @@ -437,9 +424,8 @@ func (g *HTTPClientGenerator) ComputeSpec( ) } - clientSpec, err := NewHTTPClientSpec( + clientSpec, err := clientConfig.NewClientSpec( instance, - clientConfig, g.packageHelper, ) if err != nil { @@ -546,7 +532,7 @@ func (g *TChannelClientGenerator) ComputeSpec( instance *ModuleInstance, ) (interface{}, error) { // Parse the client config from the endpoint YAML file - clientConfig, err := readClientConfig(instance.YAMLFileRaw) + clientConfig, err := newClientConfig(instance.YAMLFileRaw) if err != nil { return nil, errors.Wrapf( err, @@ -555,9 +541,8 @@ func (g *TChannelClientGenerator) ComputeSpec( ) } - clientSpec, err := NewTChannelClientSpec( + clientSpec, err := clientConfig.NewClientSpec( instance, - clientConfig, g.packageHelper, ) if err != nil { @@ -719,7 +704,7 @@ func (g *CustomClientGenerator) ComputeSpec( instance *ModuleInstance, ) (interface{}, error) { // Parse the client config from the endpoint YAML file - clientConfig, err := readClientConfig(instance.YAMLFileRaw) + clientConfig, err := newClientConfig(instance.YAMLFileRaw) if err != nil { return nil, errors.Wrapf( err, @@ -728,9 +713,8 @@ func (g *CustomClientGenerator) ComputeSpec( ) } - clientSpec, err := NewCustomClientSpec( + clientSpec, err := clientConfig.NewClientSpec( instance, - clientConfig, g.packageHelper, ) if err != nil { diff --git a/codegen/post_gen_hooks.go b/codegen/post_gen_hooks.go index 3d17892f3..666df10c2 100644 --- a/codegen/post_gen_hooks.go +++ b/codegen/post_gen_hooks.go @@ -30,7 +30,6 @@ import ( "sync/atomic" "github.com/pkg/errors" - yaml "gopkg.in/yaml.v2" ) const ( @@ -38,17 +37,6 @@ const ( custom = "custom" ) -// helper struct to pull out the fixture config -type moduleConfig struct { - Config *config `yaml:"config" json:"config"` -} - -// config is the struct corresponding to the config field in client-config.yaml -type config struct { - CustomImportPath string `yaml:"customImportPath" json:"customImportPath"` - Fixture *Fixture `yaml:"fixture" json:"fixture"` -} - // Fixture specifies client fixture import path and all scenarios type Fixture struct { // ImportPath is the package where the user-defined Fixture global variable is contained. @@ -88,8 +76,8 @@ func ClientMockGenHook(h *PackageHelper, t *Template) (PostGenHook, error) { pathSymbolMap := make(map[string]string) for _, instance := range clientInstances { key := instance.ClassType + instance.InstanceName - var mc moduleConfig - if err := yaml.Unmarshal(instance.YAMLFileRaw, &mc); err != nil { + client, errClient := newClientConfig(instance.YAMLFileRaw) + if errClient != nil { return errors.Wrapf( err, "error parsing client-config for client %q", @@ -99,13 +87,13 @@ func ClientMockGenHook(h *PackageHelper, t *Template) (PostGenHook, error) { importPath := instance.PackageInfo.GeneratedPackagePath if instance.ClassType == custom { - importPath = mc.Config.CustomImportPath + importPath = client.getCustomImportPath() } importPathMap[key] = importPath // gather all modules that need to generate fixture types - f := mc.Config.Fixture + f := client.getFixture() if f != nil && f.Scenarios != nil { pathSymbolMap[importPath] = clientInterface fixtureMap[key] = f @@ -315,16 +303,17 @@ func generateMockInitializer(instance *ModuleInstance, h *PackageHelper, t *Temp func FindClientsWithFixture(instance *ModuleInstance) (map[string]string, error) { clientsWithFixture := map[string]string{} for _, leaf := range instance.RecursiveDependencies["client"] { - var mc moduleConfig - if err := yaml.Unmarshal(leaf.YAMLFileRaw, &mc); err != nil { + client, err := newClientConfig(leaf.YAMLFileRaw) + if err != nil { return nil, errors.Wrapf( err, "error parsing client-config for client %q", instance.InstanceName, ) } - if mc.Config != nil && mc.Config.Fixture != nil { - clientsWithFixture[leaf.InstanceName] = mc.Config.Fixture.ImportPath + + if client.getFixture() != nil { + clientsWithFixture[leaf.InstanceName] = client.getFixture().ImportPath } } return clientsWithFixture, nil