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 }