1// Copyright 2017 Google LLC. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package compiler
16
17import (
18	"bytes"
19	"fmt"
20	"os/exec"
21	"strings"
22
23	"github.com/golang/protobuf/proto"
24	"github.com/golang/protobuf/ptypes/any"
25	extensions "github.com/googleapis/gnostic/extensions"
26	yaml "gopkg.in/yaml.v3"
27)
28
29// ExtensionHandler describes a binary that is called by the compiler to handle specification extensions.
30type ExtensionHandler struct {
31	Name string
32}
33
34// CallExtension calls a binary extension handler.
35func CallExtension(context *Context, in *yaml.Node, extensionName string) (handled bool, response *any.Any, err error) {
36	if context == nil || context.ExtensionHandlers == nil {
37		return false, nil, nil
38	}
39	handled = false
40	for _, handler := range *(context.ExtensionHandlers) {
41		response, err = handler.handle(in, extensionName)
42		if response == nil {
43			continue
44		} else {
45			handled = true
46			break
47		}
48	}
49	return handled, response, err
50}
51
52func (extensionHandlers *ExtensionHandler) handle(in *yaml.Node, extensionName string) (*any.Any, error) {
53	if extensionHandlers.Name != "" {
54		yamlData, _ := yaml.Marshal(in)
55		request := &extensions.ExtensionHandlerRequest{
56			CompilerVersion: &extensions.Version{
57				Major: 0,
58				Minor: 1,
59				Patch: 0,
60			},
61			Wrapper: &extensions.Wrapper{
62				Version:       "unknown", // TODO: set this to the type/version of spec being parsed.
63				Yaml:          string(yamlData),
64				ExtensionName: extensionName,
65			},
66		}
67		requestBytes, _ := proto.Marshal(request)
68		cmd := exec.Command(extensionHandlers.Name)
69		cmd.Stdin = bytes.NewReader(requestBytes)
70		output, err := cmd.Output()
71		if err != nil {
72			return nil, err
73		}
74		response := &extensions.ExtensionHandlerResponse{}
75		err = proto.Unmarshal(output, response)
76		if err != nil || !response.Handled {
77			return nil, err
78		}
79		if len(response.Errors) != 0 {
80			return nil, fmt.Errorf("Errors when parsing: %+v for field %s by vendor extension handler %s. Details %+v", in, extensionName, extensionHandlers.Name, strings.Join(response.Errors, ","))
81		}
82		return response.Value, nil
83	}
84	return nil, nil
85}
86