github.com/Axway/agent-sdk@v1.1.101/pkg/apic/specparser.go (about)

     1  package apic
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  
    10  	management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
    11  	"github.com/Axway/agent-sdk/pkg/util"
    12  	"github.com/Axway/agent-sdk/pkg/util/oas"
    13  
    14  	"github.com/Axway/agent-sdk/pkg/util/wsdl"
    15  	"github.com/emicklei/proto"
    16  	"github.com/invopop/yaml"
    17  )
    18  
    19  const (
    20  	mimeApplicationJSON = "application/json"
    21  	mimeApplicationYAML = "application/yaml"
    22  )
    23  
    24  const (
    25  	UnknownYamlJson = "unknown yaml or json based specification"
    26  )
    27  
    28  // SpecProcessor -
    29  type SpecProcessor interface {
    30  	GetVersion() string
    31  	GetEndpoints() ([]EndpointDefinition, error)
    32  	GetDescription() string
    33  	GetSpecBytes() []byte
    34  	GetResourceType() string
    35  }
    36  
    37  type AsyncSpecProcessor interface {
    38  	GetID() string
    39  	GetTitle() string
    40  	GetVersion() string
    41  	GetEndpoints() ([]management.ApiServiceInstanceSpecEndpoint, error)
    42  	GetResourceType() string
    43  	GetSpecBytes() []byte
    44  }
    45  
    46  // OasSpecProcessor -
    47  type OasSpecProcessor interface {
    48  	ParseAuthInfo()
    49  	GetAPIKeyInfo() []APIKeyInfo
    50  	GetOAuthScopes() map[string]string
    51  	GetAuthPolicies() []string
    52  	StripSpecAuth()
    53  	GetTitle() string
    54  	GetSecurityBuilder() SecurityBuilder
    55  	AddSecuritySchemes(map[string]interface{})
    56  }
    57  
    58  // SpecResourceParser -
    59  type SpecResourceParser struct {
    60  	resourceSpecType    string
    61  	resourceContentType string
    62  	resourceSpec        []byte
    63  	specProcessor       SpecProcessor
    64  	specHash            uint64
    65  }
    66  
    67  // NewSpecResourceParser -
    68  func NewSpecResourceParser(resourceSpec []byte, resourceSpecType string) SpecResourceParser {
    69  	hash, _ := util.ComputeHash(resourceSpec)
    70  	return SpecResourceParser{resourceSpec: resourceSpec, resourceSpecType: resourceSpecType, specHash: hash}
    71  }
    72  
    73  // Parse -
    74  func (s *SpecResourceParser) Parse() error {
    75  	if s.resourceSpecType == "" {
    76  		err := s.discoverSpecTypeAndCreateProcessor()
    77  		if err != nil {
    78  			return err
    79  		}
    80  	} else {
    81  		err := s.createProcessorWithResourceType()
    82  		if err != nil {
    83  			return err
    84  		}
    85  	}
    86  
    87  	if s.specProcessor == nil {
    88  		s.specProcessor = newUnstructuredSpecProcessor(s.resourceSpec)
    89  	}
    90  	return nil
    91  }
    92  
    93  func (s *SpecResourceParser) getResourceContentType() string {
    94  	return s.resourceContentType
    95  }
    96  
    97  func (s *SpecResourceParser) discoverSpecTypeAndCreateProcessor() error {
    98  	errs := []error{}
    99  	var err error
   100  	s.specProcessor, err = s.discoverYAMLAndJSONSpec()
   101  	if err == nil {
   102  		return nil
   103  	}
   104  	errs = append(errs, err)
   105  
   106  	if s.specProcessor == nil {
   107  		s.specProcessor, err = s.parseWSDLSpec()
   108  		if err == nil {
   109  			return nil
   110  		}
   111  		errs = append(errs, err)
   112  	}
   113  	if s.specProcessor == nil {
   114  		s.specProcessor, err = s.parseProtobufSpec()
   115  		if err == nil {
   116  			return nil
   117  		}
   118  		errs = append(errs, err)
   119  	}
   120  
   121  	errString := ""
   122  	for i, err := range errs {
   123  		if i > 0 {
   124  			errString += ": "
   125  		}
   126  		errString += err.Error()
   127  	}
   128  	return fmt.Errorf("could not determine spec type from file: %s", errString)
   129  
   130  }
   131  
   132  func (s *SpecResourceParser) createProcessorWithResourceType() error {
   133  	var err error
   134  	switch s.resourceSpecType {
   135  	case Wsdl:
   136  		s.specProcessor, err = s.parseWSDLSpec()
   137  	case Oas2:
   138  		s.specProcessor, err = s.parseOAS2Spec()
   139  	case Oas3:
   140  		s.specProcessor, err = s.parseOAS3Spec()
   141  	case Protobuf:
   142  		s.specProcessor, err = s.parseProtobufSpec()
   143  	case AsyncAPI:
   144  		s.specProcessor, err = s.parseAsyncAPISpec()
   145  	case GraphQL:
   146  		s.specProcessor, err = s.parseGraphQLSpec()
   147  	case Raml:
   148  		s.specProcessor, err = s.parseRamlSpec()
   149  	}
   150  	return err
   151  }
   152  
   153  // GetSpecProcessor -
   154  func (s *SpecResourceParser) GetSpecProcessor() SpecProcessor {
   155  	return s.specProcessor
   156  }
   157  
   158  func (s *SpecResourceParser) discoverYAMLAndJSONSpec() (SpecProcessor, error) {
   159  	specDef := make(map[string]interface{})
   160  	// lowercase the byte array to ensure keys we care about are parsed
   161  	contentType := mimeApplicationJSON
   162  	err := json.Unmarshal(s.resourceSpec, &specDef)
   163  	if err != nil {
   164  		contentType = mimeApplicationYAML
   165  		if err := yaml.Unmarshal(s.resourceSpec, &specDef); err != nil {
   166  			return nil, err
   167  		}
   168  	}
   169  
   170  	specType, ok := specDef["openapi"]
   171  	if ok {
   172  		openapi := specType.(string)
   173  		if strings.HasPrefix(openapi, "3.") {
   174  			s.resourceContentType = contentType
   175  			return s.parseOAS3Spec()
   176  		}
   177  		if strings.HasPrefix(openapi, "2.") {
   178  			// convert to json for parsing into openapi2.T type
   179  			specDef["swagger"] = specDef["openapi"]
   180  			s.resourceSpec, _ = json.Marshal(specDef)
   181  			return s.parseOAS2Spec()
   182  		}
   183  		return nil, errors.New("invalid openapi specification")
   184  	}
   185  
   186  	specType, ok = specDef["swagger"]
   187  	if ok {
   188  		swagger := specType.(string)
   189  		if swagger == "2.0" {
   190  			if contentType == mimeApplicationYAML {
   191  				// convert to json for parsing into openapi2.T type
   192  				s.resourceSpec, _ = json.Marshal(specDef)
   193  			}
   194  			return s.parseOAS2Spec()
   195  		}
   196  		return nil, errors.New("invalid swagger 2.0 specification")
   197  	}
   198  
   199  	_, ok = specDef["asyncapi"]
   200  	if ok {
   201  		s.resourceContentType = contentType
   202  		return newAsyncAPIProcessor(specDef, s.resourceSpec), nil
   203  	}
   204  
   205  	ramlVersion := ""
   206  	if len(s.resourceSpec) > 10 {
   207  		ramlVersion = string(s.resourceSpec[2:10])
   208  	}
   209  	if ramlVersion == Raml08 || ramlVersion == Raml10 {
   210  		s.resourceContentType = contentType
   211  		return newRamlProcessor(specDef, s.resourceSpec), nil
   212  	}
   213  
   214  	return nil, errors.New(UnknownYamlJson)
   215  }
   216  
   217  func (s *SpecResourceParser) parseWSDLSpec() (SpecProcessor, error) {
   218  	def, err := wsdl.Unmarshal(s.resourceSpec)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	return newWsdlProcessor(def, s.resourceSpec), nil
   223  }
   224  
   225  func (s *SpecResourceParser) parseGraphQLSpec() (SpecProcessor, error) {
   226  	return newGraphQLSpecProcessor(s.resourceSpec), nil
   227  }
   228  
   229  func (s *SpecResourceParser) parseOAS2Spec() (SpecProcessor, error) {
   230  	swaggerObj, err := oas.ParseOAS2(s.resourceSpec)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	return newOas2Processor(swaggerObj), nil
   235  }
   236  
   237  func (s *SpecResourceParser) parseOAS3Spec() (SpecProcessor, error) {
   238  	oas3Obj, err := oas.ParseOAS3(s.resourceSpec)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	return newOas3Processor(oas3Obj), nil
   243  }
   244  
   245  func (s *SpecResourceParser) parseAsyncAPISpec() (SpecProcessor, error) {
   246  	specDef := make(map[string]interface{})
   247  	// lowercase the byte array to ensure keys we care about are parsed
   248  	contentType := mimeApplicationJSON
   249  	err := json.Unmarshal(s.resourceSpec, &specDef)
   250  	if err != nil {
   251  		contentType = mimeApplicationYAML
   252  		err := yaml.Unmarshal(s.resourceSpec, &specDef)
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  	}
   257  	_, ok := specDef["asyncapi"]
   258  	if ok {
   259  		s.resourceContentType = contentType
   260  		return newAsyncAPIProcessor(specDef, s.resourceSpec), nil
   261  	}
   262  	return nil, errors.New("invalid asyncapi specification")
   263  }
   264  
   265  func (s *SpecResourceParser) parseProtobufSpec() (SpecProcessor, error) {
   266  	reader := bytes.NewReader(s.resourceSpec)
   267  	parser := proto.NewParser(reader)
   268  	definition, err := parser.Parse()
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	if len(definition.Elements) > 0 {
   274  		return newProtobufProcessor(definition, s.resourceSpec), nil
   275  	}
   276  	return nil, errors.New("invalid protobuf specification")
   277  
   278  }
   279  
   280  func (s *SpecResourceParser) parseRamlSpec() (SpecProcessor, error) {
   281  	specDef := make(map[string]interface{})
   282  	s.resourceContentType = mimeApplicationYAML
   283  
   284  	ramlVersion := ""
   285  	if len(s.resourceSpec) > 10 {
   286  		ramlVersion = string(s.resourceSpec[2:10])
   287  	}
   288  	if ramlVersion != Raml08 && ramlVersion != Raml10 {
   289  		return nil, errors.New("invalid RAML specification")
   290  	}
   291  
   292  	err := yaml.Unmarshal(s.resourceSpec, &specDef)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	return newRamlProcessor(specDef, s.resourceSpec), nil
   298  }