11package parser
22
33import (
4- "bytes"
54 "regexp"
65 "strconv"
76 "strings"
87
98 "emperror.dev/errors"
10- "github.com/Jeffail/gabs/v2"
119 "github.com/apex/log"
1210 "github.com/buger/jsonparser"
1311 "github.com/iancoleman/strcase"
12+ "github.com/tidwall/gjson"
13+ "github.com/tidwall/sjson"
1414)
1515
1616// Regex to match anything that has a value matching the format of {{ config.$1 }} which
@@ -62,12 +62,13 @@ func (cfr *ConfigurationFileReplacement) getKeyValue(value string) interface{} {
6262// This does not currently support nested wildcard matches. For example, foo.*.bar
6363// will work, however foo.*.bar.*.baz will not, since we'll only be splitting at the
6464// first wildcard, and not subsequent ones.
65- func (f * ConfigurationFile ) IterateOverJson (data []byte ) (* gabs.Container , error ) {
66- parsed , err := gabs .ParseJSON (data )
67- if err != nil {
68- return nil , err
65+ func (f * ConfigurationFile ) IterateOverJson (data []byte ) ([]byte , error ) {
66+ if ! gjson .ValidBytes (data ) {
67+ return nil , errors .New ("invalid JSON data" )
6968 }
7069
70+ jsonStr := string (data )
71+
7172 for _ , v := range f .Replace {
7273 value , err := f .LookupConfigurationValue (v )
7374 if err != nil {
@@ -78,140 +79,109 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
7879 // begin doing a search and replace in the data.
7980 if strings .Contains (v .Match , ".*" ) {
8081 parts := strings .SplitN (v .Match , ".*" , 2 )
82+ basePath := strings .Trim (parts [0 ], "." )
83+ remainingPath := strings .Trim (parts [1 ], "." )
84+
85+ result := gjson .Get (jsonStr , basePath )
86+ if ! result .Exists () {
87+ continue
88+ }
8189
82- // Iterate over each matched child and set the remaining path to the value
83- // that is passed through in the loop.
84- //
85- // If the child is a null value, nothing will happen. Seems reasonable as of the
86- // time this code is being written.
87- for _ , child := range parsed .Path (strings .Trim (parts [0 ], "." )).Children () {
88- if err := v .SetAtPathway (child , strings .Trim (parts [1 ], "." ), value ); err != nil {
89- if errors .Is (err , gabs .ErrNotFound ) {
90- continue
90+ if result .IsArray () {
91+ result .ForEach (func (key , val gjson.Result ) bool {
92+ fullPath := basePath + "." + key .String ()
93+ if remainingPath != "" {
94+ fullPath += "." + remainingPath
9195 }
96+ var setErr error
97+ jsonStr , setErr = v .setValueWithSjson (jsonStr , fullPath , value )
98+ if setErr != nil {
99+ err = setErr
100+ return false
101+ }
102+ return true
103+ })
104+ if err != nil {
92105 return nil , errors .WithMessage (err , "failed to set config value of array child" )
93106 }
107+ } else if result .IsObject () {
108+ result .ForEach (func (key , val gjson.Result ) bool {
109+ fullPath := basePath + "." + key .String ()
110+ if remainingPath != "" {
111+ fullPath += "." + remainingPath
112+ }
113+ var setErr error
114+ jsonStr , setErr = v .setValueWithSjson (jsonStr , fullPath , value )
115+ if setErr != nil {
116+ err = setErr
117+ return false
118+ }
119+ return true
120+ })
121+ if err != nil {
122+ return nil , errors .WithMessage (err , "failed to set config value of object child" )
123+ }
94124 }
95125 continue
96126 }
97127
98- if err := v .SetAtPathway (parsed , v .Match , value ); err != nil {
99- if errors .Is (err , gabs .ErrNotFound ) {
128+ var setErr error
129+ jsonStr , setErr = v .setValueWithSjson (jsonStr , v .Match , value )
130+ if setErr != nil {
131+ if strings .Contains (setErr .Error (), "path not found" ) {
100132 continue
101133 }
102- return nil , errors .WithMessage (err , "unable to set config value at pathway: " + v .Match )
134+ return nil , errors .WithMessage (setErr , "unable to set config value at pathway: " + v .Match )
103135 }
104136 }
105137
106- return parsed , nil
138+ return [] byte ( jsonStr ) , nil
107139}
108140
109- // Regex used to check if there is an array element present in the given pathway by looking for something
110- // along the lines of "something[1]" or "something[1].nestedvalue" as the path.
111- var checkForArrayElement = regexp .MustCompile (`^([^\[\]]+)\[([\d]+)](\..+)?$` )
112-
113- // Attempt to set the value of the path depending on if it is an array or not. Gabs cannot handle array
114- // values as "something[1]" but can parse them just fine. This is basically just overly complex code
115- // to handle that edge case and ensure the value gets set correctly.
116- //
117- // Bless thee who has to touch these most unholy waters.
118- func setValueAtPath (c * gabs.Container , path string , value interface {}) error {
119- var err error
120-
121- matches := checkForArrayElement .FindStringSubmatch (path )
122-
123- // Check if we are **NOT** updating an array element.
124- if len (matches ) < 3 {
125- _ , err = c .SetP (value , path )
126- return err
127- }
141+ func (cfr * ConfigurationFileReplacement ) setValueWithSjson (jsonStr string , path string , value string ) (string , error ) {
142+ if cfr .IfValue != "" {
143+ // Check if we are replacing instead of overwriting.
144+ if strings .HasPrefix (cfr .IfValue , "regex:" ) {
145+ result := gjson .Get (jsonStr , path )
146+ if ! result .Exists () {
147+ return jsonStr , nil
148+ }
128149
129- i , _ := strconv .Atoi (matches [2 ])
130- // Find the array element "i" or try to create it if "i" is equal to 0 and is not found
131- // at the given path.
132- ct , err := c .ArrayElementP (i , matches [1 ])
133- if err != nil {
134- if i != 0 || (! errors .Is (err , gabs .ErrNotArray ) && ! errors .Is (err , gabs .ErrNotFound )) {
135- return errors .WithMessage (err , "error while parsing array element at path" )
136- }
150+ r , err := regexp .Compile (strings .TrimPrefix (cfr .IfValue , "regex:" ))
151+ if err != nil {
152+ log .WithFields (log.Fields {"if_value" : strings .TrimPrefix (cfr .IfValue , "regex:" ), "error" : err }).
153+ Warn ("configuration if_value using invalid regexp, cannot perform replacement" )
154+ return jsonStr , nil
155+ }
137156
138- t := make ([] interface {}, 1 )
139- // If the length of matches is 4 it means we're trying to access an object down in this array
140- // key, so make sure we generate the array as an array of objects, and not just a generic nil
141- // array.
142- if len ( matches ) == 4 {
143- t = [] interface {}{ map [ string ] interface {}{}}
157+ v := result . String ( )
158+ if r . MatchString ( v ) {
159+ newValue := r . ReplaceAllString ( v , value )
160+ return sjson . Set ( jsonStr , path , newValue )
161+ }
162+ return jsonStr , nil
144163 }
145164
146- // If the error is because this isn't an array or isn't found go ahead and create the array with
147- // an empty object if we have additional things to set on the array, or just an empty array type
148- // if there is not an object structure detected (no matches[3] available).
149- if _ , err = c .SetP (t , matches [1 ]); err != nil {
150- return errors .WithMessage (err , "failed to create empty array for missing element" )
165+ result := gjson .Get (jsonStr , path )
166+ if ! result .Exists () {
167+ return jsonStr , nil
151168 }
152-
153- // Set our cursor to be the array element we expect, which in this case is just the first element
154- // since we won't run this code unless the array element is 0. There is too much complexity in trying
155- // to match additional elements. In those cases the server will just have to be rebooted or something.
156- ct , err = c .ArrayElementP (0 , matches [1 ])
157- if err != nil {
158- return errors .WithMessage (err , "failed to find array element at path" )
169+ if result .String () != cfr .IfValue {
170+ return jsonStr , nil
159171 }
160172 }
161173
162- // Try to set the value. If the path does not exist an error will be raised to the caller which will
163- // then check if the error is because the path is missing. In those cases we just ignore the error since
164- // we don't want to do anything specifically when that happens.
165- //
166- // If there are four matches in the regex it means that we managed to also match a trailing pathway
167- // for the key, which should be found in the given array key item and modified further.
168- if len (matches ) == 4 {
169- _ , err = ct .SetP (value , strings .TrimPrefix (matches [3 ], "." ))
174+ var setValue interface {}
175+ if cfr .ReplaceWith .Type () == jsonparser .Boolean {
176+ v , _ := strconv .ParseBool (value )
177+ setValue = v
178+ } else if v , err := strconv .Atoi (value ); err == nil {
179+ setValue = v
170180 } else {
171- _ , err = ct .Set (value )
172- }
173-
174- if err != nil {
175- return errors .WithMessage (err , "failed to set value at config path: " + path )
176- }
177-
178- return nil
179- }
180-
181- // Sets the value at a specific pathway, but checks if we were looking for a specific
182- // value or not before doing it.
183- func (cfr * ConfigurationFileReplacement ) SetAtPathway (c * gabs.Container , path string , value string ) error {
184- if cfr .IfValue == "" {
185- return setValueAtPath (c , path , cfr .getKeyValue (value ))
186- }
187-
188- // Check if we are replacing instead of overwriting.
189- if strings .HasPrefix (cfr .IfValue , "regex:" ) {
190- // Doing a regex replacement requires an existing value.
191- // TODO: Do we try passing an empty string to the regex?
192- if c .ExistsP (path ) {
193- return gabs .ErrNotFound
194- }
195-
196- r , err := regexp .Compile (strings .TrimPrefix (cfr .IfValue , "regex:" ))
197- if err != nil {
198- log .WithFields (log.Fields {"if_value" : strings .TrimPrefix (cfr .IfValue , "regex:" ), "error" : err }).
199- Warn ("configuration if_value using invalid regexp, cannot perform replacement" )
200- return nil
201- }
202-
203- v := strings .Trim (c .Path (path ).String (), "\" " )
204- if r .Match ([]byte (v )) {
205- return setValueAtPath (c , path , r .ReplaceAllString (v , value ))
206- }
207- return nil
208- }
209-
210- if c .ExistsP (path ) && ! bytes .Equal (c .Bytes (), []byte (cfr .IfValue )) {
211- return nil
181+ setValue = value
212182 }
213183
214- return setValueAtPath ( c , path , cfr . getKeyValue ( value ) )
184+ return sjson . Set ( jsonStr , path , setValue )
215185}
216186
217187// Looks up a configuration value on the Daemon given a dot-notated syntax.
0 commit comments