pkg/sdk/convert/yaml.go (90 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package convert import ( "bytes" "encoding/json" "errors" "fmt" "io" "reflect" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "sigs.k8s.io/yaml" "github.com/elastic/harp/pkg/sdk/types" ) // YAMLtoJSON reads a given reader in order to extract a JSON representation func YAMLtoJSON(r io.Reader) (io.Reader, error) { // Check arguments if types.IsNil(r) { return nil, fmt.Errorf("reader is nil") } // Drain the reader jsonReader, err := loadFromYAML(r) if err != nil { return nil, fmt.Errorf("unable to parse YAML input: %w", err) } // No error return jsonReader, nil } // PBtoYAML converts a protobuf object to a YAML representation func PBtoYAML(msg proto.Message) ([]byte, error) { // Check arguments if types.IsNil(msg) { return nil, fmt.Errorf("msg is nil") } // Encode protbuf message as JSON pb, err := protojson.Marshal(msg) if err != nil { return nil, fmt.Errorf("unable to encode protbuf message to JSON: %w", err) } // Decode input as JSON var jsonObj interface{} if errDecode := yaml.Unmarshal(pb, &jsonObj); errDecode != nil { return nil, fmt.Errorf("unable to decode JSON input: %w", errDecode) } // Marshal as YAML out, errEncode := yaml.Marshal(jsonObj) if errEncode != nil { return nil, fmt.Errorf("unable to produce YAML output: %w", errEncode) } // No error return out, nil } // ----------------------------------------------------------------------------- // loadFromYAML reads YAML definition and returns the PB struct. // // Protobuf doesn't contain YAML struct tags and json one are not symetric // to protobuf. We need to export YAML as JSON, and then read JSON to Protobuf // as done in k8s yaml loader. func loadFromYAML(r io.Reader) (io.Reader, error) { // Check arguments if types.IsNil(r) { return nil, fmt.Errorf("reader is nil") } // Drain input reader in, err := io.ReadAll(r) if err != nil && !errors.Is(err, io.EOF) { return nil, fmt.Errorf("unable to drain input reader: %w", err) } // Decode as YAML any object var specBody interface{} if errYaml := yaml.Unmarshal(in, &specBody); errYaml != nil { return nil, fmt.Errorf("unable to decode spec as YAML: %w", err) } // Convert map[interface{}]interface{} to a JSON serializable struct specBody, err = convertMapStringInterface(specBody) if err != nil { return nil, fmt.Errorf("unable to prepare spec to json transformation: %w", err) } // Marshal as json jsonData, err := json.Marshal(specBody) if err != nil { return nil, fmt.Errorf("unable ot marshal spec as JSON: %w", err) } // No error return bytes.NewReader(jsonData), nil } // Converts map[interface{}]interface{} into map[string]interface{} for json.Marshaler func convertMapStringInterface(val interface{}) (interface{}, error) { switch items := val.(type) { case map[interface{}]interface{}: result := map[string]interface{}{} for k, v := range items { key, ok := k.(string) if !ok { return nil, fmt.Errorf("TypeError: value %s (type `%s') can't be assigned to type 'string'", k, reflect.TypeOf(k)) } value, err := convertMapStringInterface(v) if err != nil { return nil, fmt.Errorf("unable to convert map[string] to map[interface{}]: %w", err) } result[key] = value } return result, nil case []interface{}: for k, v := range items { value, err := convertMapStringInterface(v) if err != nil { return nil, fmt.Errorf("unable to convert map[string] to map[interface{}]: %w", err) } items[k] = value } } return val, nil }