Skip to content

Commit

Permalink
Merge abe0abf into d937584
Browse files Browse the repository at this point in the history
  • Loading branch information
l-w-2017 committed Nov 5, 2018
2 parents d937584 + abe0abf commit e52bd29
Show file tree
Hide file tree
Showing 5 changed files with 950 additions and 61 deletions.
309 changes: 309 additions & 0 deletions 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)
}
}

0 comments on commit e52bd29

Please sign in to comment.