github.com/actions-on-google/gactions@v3.2.0+incompatible/api/yamlutils.go (about) 1 // Copyright 2020 Google LLC 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 // https://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 15 // Package yamlutils provides utility methods to convert Yaml files to SDK protos. 16 package yamlutils 17 18 import ( 19 "errors" 20 "fmt" 21 "time" 22 23 "gopkg.in/yaml.v2" 24 ) 25 26 var unmarshal = yaml.Unmarshal 27 28 // UnmarshalYAMLToMap unmarshalls Yaml file into a map[string]interface{} that can be decoded into JSON. 29 // The implementation has been copied over with slight modifications from a standard template. 30 // The function returns a JSON representation instead of the Proto as it's done in the referenced file. 31 func UnmarshalYAMLToMap(data []byte) (map[string]interface{}, error) { 32 errCh := make(chan error) 33 ch := make(chan map[string]interface{}) 34 go func() { 35 // The yaml library can panic. 36 // Add a recover() here to handle this gracefully. 37 defer func() { 38 if r := recover(); r != nil { 39 errCh <- errors.New("panic caught: invalid yaml file") 40 } 41 }() 42 var m map[string]interface{} 43 if err := unmarshal(data, &m); err != nil { 44 errCh <- err 45 return 46 } 47 ch <- m 48 }() 49 50 var m map[string]interface{} 51 select { 52 case err := <-errCh: 53 return nil, err 54 case m = <-ch: 55 break 56 case <-time.After(10 * time.Second): 57 return nil, errors.New("unmarshal took too long") 58 } 59 // fix is guaranteed to modify m to make it the right type. 60 return fix(m).(map[string]interface{}), nil 61 } 62 63 // YAML unmarshalling produces a map[string]interface{} where the value might 64 // be a map[interface{}]interface{}, or a []interface{} where values might be a 65 // map[interface{}]interface{}, which json.Marshal does not support. 66 // 67 // So we have to go through the map and change any map[interface{}]interface{} 68 // we find into a map[string]interface{}, which JSON decoding supports. 69 // 70 // In order to make it compatible with our use case, the keys in the JSON object 71 // are converted from snake_case to camelCase. 72 func fix(in interface{}) interface{} { 73 switch in.(type) { 74 case map[interface{}]interface{}: 75 // Create a new map[string]interface{} and fill it with fixed keys. 76 cp := map[string]interface{}{} 77 for k, v := range in.(map[interface{}]interface{}) { 78 cp[fmt.Sprintf("%s", k)] = v 79 } 80 // Now fix the map[string]interface{} to fix the values. 81 return fix(cp) 82 case map[string]interface{}: 83 // Fix each value in the map. 84 sm := in.(map[string]interface{}) 85 cp := map[string]interface{}{} 86 for k, v := range sm { 87 cp[k] = fix(v) 88 } 89 return cp 90 case []interface{}: 91 // Fix each element in the slice. 92 s := in.([]interface{}) 93 for i, v := range s { 94 s[i] = fix(v) 95 } 96 return s 97 default: 98 // Value doesn't need to be fixed. If this is not a supported type, JSON 99 // encoding will fail. 100 return in 101 } 102 }