patch.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. // Package patch provides high-performance JSON request patching functionality using sonic.
  2. // It allows automatic modification of API requests based on conditions and rules.
  3. package patch
  4. import (
  5. "fmt"
  6. "net/http"
  7. "regexp"
  8. "slices"
  9. "strconv"
  10. "strings"
  11. "github.com/bytedance/sonic"
  12. "github.com/bytedance/sonic/ast"
  13. "github.com/labring/aiproxy/core/common"
  14. "github.com/labring/aiproxy/core/relay/adaptor"
  15. "github.com/labring/aiproxy/core/relay/meta"
  16. "github.com/labring/aiproxy/core/relay/plugin"
  17. "github.com/labring/aiproxy/core/relay/plugin/noop"
  18. )
  19. var _ plugin.Plugin = (*Plugin)(nil)
  20. const PluginName = "patch"
  21. // LazyPatchData represents data to be applied by patch plugin later
  22. type LazyPatchData struct {
  23. Source string `json:"source"` // Source plugin name
  24. Data any `json:"data"` // Data to be patched
  25. }
  26. const lazyPatchesKey = "_lazy_patches"
  27. // Plugin implements JSON request patching functionality
  28. type Plugin struct {
  29. noop.Noop
  30. }
  31. // NewPatchPlugin creates a new patch plugin instance
  32. func NewPatchPlugin() *Plugin {
  33. return &Plugin{}
  34. }
  35. // AddLazyPatch adds data to the lazy patch queue in meta
  36. func AddLazyPatch(meta *meta.Meta, patch PatchOperation) {
  37. meta.PushToSlice(lazyPatchesKey, patch)
  38. }
  39. // GetLazyPatches retrieves all lazy patch data from meta
  40. func GetLazyPatches(meta *meta.Meta) []PatchOperation {
  41. slice := meta.GetSlice(lazyPatchesKey)
  42. if slice == nil {
  43. return nil
  44. }
  45. patches := make([]PatchOperation, 0, len(slice))
  46. for _, item := range slice {
  47. if patch, ok := item.(PatchOperation); ok {
  48. patches = append(patches, patch)
  49. }
  50. }
  51. return patches
  52. }
  53. // ConvertRequest applies JSON patches to the request body
  54. func (p *Plugin) ConvertRequest(
  55. meta *meta.Meta,
  56. store adaptor.Store,
  57. req *http.Request,
  58. do adaptor.ConvertRequest,
  59. ) (adaptor.ConvertResult, error) {
  60. // Load patch configuration from model config
  61. config := p.loadConfig(meta)
  62. bodyBytes, err := common.GetRequestBodyReusable(req)
  63. if err != nil {
  64. return do.ConvertRequest(meta, store, req)
  65. }
  66. // Apply patches
  67. patchedBody, modified, err := p.ApplyPatches(bodyBytes, meta, config)
  68. if err != nil {
  69. return do.ConvertRequest(meta, store, req)
  70. }
  71. // If no modifications were made, return original
  72. if !modified {
  73. return do.ConvertRequest(meta, store, req)
  74. }
  75. common.SetRequestBody(req, patchedBody)
  76. defer func() {
  77. common.SetRequestBody(req, bodyBytes)
  78. }()
  79. return do.ConvertRequest(meta, store, req)
  80. }
  81. // loadConfig loads patch configuration from model config
  82. func (p *Plugin) loadConfig(meta *meta.Meta) *Config {
  83. // Load plugin config from model config
  84. var config Config
  85. if err := meta.ModelConfig.LoadPluginConfig(PluginName, &config); err != nil {
  86. return &Config{}
  87. }
  88. return &config
  89. }
  90. // ApplyPatches applies all applicable patches to the JSON body
  91. func (p *Plugin) ApplyPatches(
  92. bodyBytes []byte,
  93. meta *meta.Meta,
  94. config *Config,
  95. ) ([]byte, bool, error) {
  96. // Parse JSON using sonic AST
  97. node, err := sonic.Get(bodyBytes)
  98. if err != nil {
  99. // If it's not valid JSON, return as is
  100. return bodyBytes, false, nil
  101. }
  102. modified := false
  103. // Apply predefined patches (always enabled)
  104. for _, patch := range DefaultPredefinedPatches {
  105. if p.shouldApplyPatch(&patch, &node, meta) {
  106. if p.applyPatch(&patch, &node) {
  107. modified = true
  108. }
  109. }
  110. }
  111. // Apply lazy patches from meta
  112. if p.applyLazyPatches(&node, meta) {
  113. modified = true
  114. }
  115. // Apply user-defined patches
  116. for _, patch := range config.UserPatches {
  117. if p.shouldApplyPatch(&patch, &node, meta) {
  118. if p.applyPatch(&patch, &node) {
  119. modified = true
  120. }
  121. }
  122. }
  123. if !modified {
  124. return bodyBytes, false, nil
  125. }
  126. // Marshal back to JSON using sonic
  127. patchedBytes, err := node.MarshalJSON()
  128. if err != nil {
  129. return bodyBytes, false, fmt.Errorf("failed to marshal patched JSON: %w", err)
  130. }
  131. return patchedBytes, true, nil
  132. }
  133. // shouldApplyPatch determines if a patch should be applied based on conditions
  134. func (p *Plugin) shouldApplyPatch(patch *PatchRule, root *ast.Node, meta *meta.Meta) bool {
  135. // Check if the patch has conditions
  136. if len(patch.Conditions) == 0 {
  137. return true // No conditions means always apply
  138. }
  139. // Default to "and" logic if not specified
  140. logic := patch.ConditionLogic
  141. if logic == "" {
  142. logic = LogicAnd
  143. }
  144. switch logic {
  145. case LogicOr:
  146. // At least one condition must be satisfied
  147. for _, condition := range patch.Conditions {
  148. if p.evaluateCondition(&condition, root, meta) {
  149. return true
  150. }
  151. }
  152. return false
  153. case LogicAnd:
  154. fallthrough
  155. default:
  156. // All conditions must be satisfied
  157. for _, condition := range patch.Conditions {
  158. if !p.evaluateCondition(&condition, root, meta) {
  159. return false
  160. }
  161. }
  162. return true
  163. }
  164. }
  165. // evaluateCondition evaluates a single condition
  166. func (p *Plugin) evaluateCondition(
  167. condition *PatchCondition,
  168. root *ast.Node,
  169. meta *meta.Meta,
  170. ) bool {
  171. var actualValue any
  172. // Get the value to check
  173. switch condition.Key {
  174. case "model":
  175. actualValue = meta.ActualModel
  176. case "original_model":
  177. actualValue = meta.OriginModel
  178. default:
  179. // Look in JSON data
  180. actualValue = p.getNestedValueAST(root, condition.Key)
  181. }
  182. // Convert to string for comparison
  183. actualStr := fmt.Sprintf("%v", actualValue)
  184. var result bool
  185. // Apply the operator
  186. switch condition.Operator {
  187. case OperatorEquals:
  188. result = actualStr == condition.Value
  189. case OperatorNotEquals:
  190. result = actualStr != condition.Value
  191. case OperatorContains:
  192. result = strings.Contains(actualStr, condition.Value)
  193. case OperatorNotContains:
  194. result = !strings.Contains(actualStr, condition.Value)
  195. case OperatorHasPrefix:
  196. result = strings.HasPrefix(actualStr, condition.Value)
  197. case OperatorHasSuffix:
  198. result = strings.HasSuffix(actualStr, condition.Value)
  199. case OperatorRegex:
  200. matched, err := regexp.MatchString(condition.Value, actualStr)
  201. result = err == nil && matched
  202. case OperatorExists:
  203. result = actualValue != nil
  204. case OperatorNotExists:
  205. result = actualValue == nil
  206. case OperatorGreaterThan:
  207. result = p.compareNumeric(actualValue, condition.Value, ">")
  208. case OperatorLessThan:
  209. result = p.compareNumeric(actualValue, condition.Value, "<")
  210. case OperatorGreaterEq:
  211. result = p.compareNumeric(actualValue, condition.Value, ">=")
  212. case OperatorLessEq:
  213. result = p.compareNumeric(actualValue, condition.Value, "<=")
  214. case OperatorIn:
  215. result = p.stringInSlice(actualStr, condition.Values)
  216. case OperatorNotIn:
  217. result = !p.stringInSlice(actualStr, condition.Values)
  218. default:
  219. result = false
  220. }
  221. // Apply negation if specified
  222. if condition.Negate {
  223. result = !result
  224. }
  225. return result
  226. }
  227. // applyPatch applies a single patch to the JSON data
  228. func (p *Plugin) applyPatch(patch *PatchRule, root *ast.Node) bool {
  229. modified := false
  230. for _, operation := range patch.Operations {
  231. operationModified, err := p.applyOperation(&operation, root)
  232. if err == nil && operationModified {
  233. modified = true
  234. }
  235. }
  236. return modified
  237. }
  238. // applyOperation applies a single operation
  239. func (p *Plugin) applyOperation(operation *PatchOperation, root *ast.Node) (bool, error) {
  240. // Resolve placeholders in the value
  241. resolvedValue := p.resolvePlaceholdersAST(operation.Value, root)
  242. switch operation.Op {
  243. case OpSet:
  244. return p.setValueAST(root, operation.Key, resolvedValue), nil
  245. case OpDelete:
  246. return p.deleteValueAST(root, operation.Key), nil
  247. case OpAdd:
  248. // For add, we only set if the key doesn't exist
  249. if p.getNestedValueAST(root, operation.Key) == nil {
  250. return p.setValueAST(root, operation.Key, resolvedValue), nil
  251. }
  252. return false, nil
  253. case OpLimit:
  254. return p.limitValueAST(root, operation.Key, resolvedValue), nil
  255. case OpIncrement:
  256. return p.incrementValueAST(root, operation.Key, resolvedValue), nil
  257. case OpDecrement:
  258. return p.decrementValueAST(root, operation.Key, resolvedValue), nil
  259. case OpMultiply:
  260. return p.multiplyValueAST(root, operation.Key, resolvedValue), nil
  261. case OpDivide:
  262. return p.divideValueAST(root, operation.Key, resolvedValue), nil
  263. case OpAppend:
  264. return p.appendValueAST(root, operation.Key, resolvedValue), nil
  265. case OpPrepend:
  266. return p.prependValueAST(root, operation.Key, resolvedValue), nil
  267. case OpFunction:
  268. return operation.Function(root)
  269. default:
  270. return false, nil
  271. }
  272. }
  273. // getNestedValueAST retrieves a value from nested JSON structure using AST
  274. func (p *Plugin) getNestedValueAST(root *ast.Node, key string) any {
  275. keys := strings.Split(key, ".")
  276. current := root
  277. for _, k := range keys {
  278. if current.TypeSafe() != ast.V_OBJECT {
  279. return nil
  280. }
  281. next := current.Get(k)
  282. if !next.Valid() {
  283. return nil
  284. }
  285. current = next
  286. }
  287. // Convert AST node to interface{}
  288. val, _ := current.Interface()
  289. return val
  290. }
  291. // setValueAST sets a value in nested JSON structure using AST
  292. func (p *Plugin) setValueAST(root *ast.Node, key string, value any) bool {
  293. keys := strings.Split(key, ".")
  294. current := root
  295. // Navigate to the parent of the target key
  296. for i := range len(keys) - 1 {
  297. if current.TypeSafe() != ast.V_OBJECT {
  298. return false
  299. }
  300. next := current.Get(keys[i])
  301. if !next.Valid() {
  302. // Create new object if it doesn't exist
  303. newObj := ast.NewObject([]ast.Pair{})
  304. if _, err := current.Set(keys[i], newObj); err != nil {
  305. return false
  306. }
  307. next = current.Get(keys[i])
  308. }
  309. current = next
  310. }
  311. if current.TypeSafe() != ast.V_OBJECT {
  312. return false
  313. }
  314. finalKey := keys[len(keys)-1]
  315. oldValue := current.Get(finalKey)
  316. // Capture the old value BEFORE we modify the node
  317. var (
  318. oldVal any
  319. hasOldValue bool
  320. )
  321. if oldValue.Valid() {
  322. oldVal, _ = oldValue.Interface()
  323. hasOldValue = true
  324. } else {
  325. hasOldValue = false
  326. }
  327. // Create AST node from value
  328. var newNode ast.Node
  329. if value == nil {
  330. newNode = ast.NewNull()
  331. } else {
  332. switch v := value.(type) {
  333. case string:
  334. newNode = ast.NewString(v)
  335. case int:
  336. newNode = ast.NewNumber(strconv.Itoa(v))
  337. case int64:
  338. newNode = ast.NewNumber(strconv.FormatInt(v, 10))
  339. case float64:
  340. newNode = ast.NewNumber(strconv.FormatFloat(v, 'f', -1, 64))
  341. case bool:
  342. newNode = ast.NewBool(v)
  343. default:
  344. // Try to marshal and parse
  345. if bytes, err := sonic.Marshal(v); err == nil {
  346. if node, err := sonic.Get(bytes); err == nil {
  347. newNode = node
  348. } else {
  349. return false
  350. }
  351. } else {
  352. return false
  353. }
  354. }
  355. }
  356. if _, err := current.Set(finalKey, newNode); err != nil {
  357. return false
  358. }
  359. // Check if value actually changed
  360. if hasOldValue {
  361. newVal, _ := newNode.Interface()
  362. changed := fmt.Sprintf("%v", oldVal) != fmt.Sprintf("%v", newVal)
  363. return changed
  364. }
  365. return true
  366. }
  367. // deleteValueAST deletes a value from nested JSON structure using AST
  368. func (p *Plugin) deleteValueAST(root *ast.Node, key string) bool {
  369. keys := strings.Split(key, ".")
  370. current := root
  371. // Navigate to the parent of the target key
  372. for i := range len(keys) - 1 {
  373. if current.TypeSafe() != ast.V_OBJECT {
  374. return false
  375. }
  376. next := current.Get(keys[i])
  377. if !next.Valid() {
  378. return false
  379. }
  380. current = next
  381. }
  382. if current.TypeSafe() != ast.V_OBJECT {
  383. return false
  384. }
  385. finalKey := keys[len(keys)-1]
  386. oldValue := current.Get(finalKey)
  387. if !oldValue.Valid() {
  388. return false
  389. }
  390. if _, err := current.Unset(finalKey); err != nil {
  391. return false
  392. }
  393. return true
  394. }
  395. // limitValueAST limits a numeric value to a maximum using AST
  396. func (p *Plugin) limitValueAST(root *ast.Node, key string, maxValue any) bool {
  397. currentValue := p.getNestedValueAST(root, key)
  398. if currentValue == nil {
  399. return false
  400. }
  401. // Convert values to float64 for comparison
  402. currentFloat, err := ToFloat64(currentValue)
  403. if err != nil {
  404. return false
  405. }
  406. maxFloat, err := ToFloat64(maxValue)
  407. if err != nil {
  408. return false
  409. }
  410. // If current value exceeds the limit, set it to the limit
  411. if currentFloat > maxFloat {
  412. result := p.setValueAST(root, key, maxValue)
  413. return result
  414. }
  415. return false
  416. }
  417. // incrementValueAST increments a numeric value using AST
  418. func (p *Plugin) incrementValueAST(root *ast.Node, key string, incrementValue any) bool {
  419. currentValue := p.getNestedValueAST(root, key)
  420. if currentValue == nil {
  421. return false
  422. }
  423. currentFloat, err := ToFloat64(currentValue)
  424. if err != nil {
  425. return false
  426. }
  427. incrementFloat, err := ToFloat64(incrementValue)
  428. if err != nil {
  429. return false
  430. }
  431. newValue := currentFloat + incrementFloat
  432. return p.setValueAST(root, key, newValue)
  433. }
  434. // decrementValueAST decrements a numeric value using AST
  435. func (p *Plugin) decrementValueAST(root *ast.Node, key string, decrementValue any) bool {
  436. currentValue := p.getNestedValueAST(root, key)
  437. if currentValue == nil {
  438. return false
  439. }
  440. currentFloat, err := ToFloat64(currentValue)
  441. if err != nil {
  442. return false
  443. }
  444. decrementFloat, err := ToFloat64(decrementValue)
  445. if err != nil {
  446. return false
  447. }
  448. newValue := currentFloat - decrementFloat
  449. return p.setValueAST(root, key, newValue)
  450. }
  451. // multiplyValueAST multiplies a numeric value using AST
  452. func (p *Plugin) multiplyValueAST(root *ast.Node, key string, multiplierValue any) bool {
  453. currentValue := p.getNestedValueAST(root, key)
  454. if currentValue == nil {
  455. return false
  456. }
  457. currentFloat, err := ToFloat64(currentValue)
  458. if err != nil {
  459. return false
  460. }
  461. multiplierFloat, err := ToFloat64(multiplierValue)
  462. if err != nil {
  463. return false
  464. }
  465. newValue := currentFloat * multiplierFloat
  466. return p.setValueAST(root, key, newValue)
  467. }
  468. // divideValueAST divides a numeric value using AST
  469. func (p *Plugin) divideValueAST(root *ast.Node, key string, divisorValue any) bool {
  470. currentValue := p.getNestedValueAST(root, key)
  471. if currentValue == nil {
  472. return false
  473. }
  474. currentFloat, err := ToFloat64(currentValue)
  475. if err != nil {
  476. return false
  477. }
  478. divisorFloat, err := ToFloat64(divisorValue)
  479. if err != nil || divisorFloat == 0 {
  480. return false
  481. }
  482. newValue := currentFloat / divisorFloat
  483. return p.setValueAST(root, key, newValue)
  484. }
  485. // appendValueAST appends a value to an array using AST
  486. func (p *Plugin) appendValueAST(root *ast.Node, key string, value any) bool {
  487. currentNode, exists := p.getNodeByKey(root, key)
  488. if !exists {
  489. // Create new array with the value
  490. valueNode := p.createASTNode(value)
  491. if !valueNode.Valid() {
  492. return false
  493. }
  494. newArray := ast.NewArray([]ast.Node{valueNode})
  495. return p.setValueAST(root, key, newArray)
  496. }
  497. if currentNode.TypeSafe() != ast.V_ARRAY {
  498. return false
  499. }
  500. valueNode := p.createASTNode(value)
  501. if !valueNode.Valid() {
  502. return false
  503. }
  504. if err := currentNode.Add(valueNode); err != nil {
  505. return false
  506. }
  507. return true
  508. }
  509. // prependValueAST prepends a value to an array using AST
  510. func (p *Plugin) prependValueAST(root *ast.Node, key string, value any) bool {
  511. currentNode, exists := p.getNodeByKey(root, key)
  512. if !exists {
  513. // Create new array with the value
  514. valueNode := p.createASTNode(value)
  515. if !valueNode.Valid() {
  516. return false
  517. }
  518. newArray := ast.NewArray([]ast.Node{valueNode})
  519. return p.setValueAST(root, key, newArray)
  520. }
  521. if currentNode.TypeSafe() != ast.V_ARRAY {
  522. return false
  523. }
  524. valueNode := p.createASTNode(value)
  525. if !valueNode.Valid() {
  526. return false
  527. }
  528. // Get all existing elements
  529. length, err := currentNode.Len()
  530. if err != nil {
  531. return false
  532. }
  533. elements := make([]ast.Node, length+1)
  534. elements[0] = valueNode
  535. for i := range length {
  536. elem := currentNode.Index(i)
  537. if elem == nil {
  538. return false
  539. }
  540. elements[i+1] = *elem
  541. }
  542. // Rebuild array
  543. newArray := ast.NewArray(elements)
  544. return p.setValueAST(root, key, newArray)
  545. }
  546. // getNodeByKey gets an AST node by key path
  547. func (p *Plugin) getNodeByKey(root *ast.Node, key string) (ast.Node, bool) {
  548. keys := strings.Split(key, ".")
  549. current := root
  550. for _, k := range keys {
  551. if current.TypeSafe() != ast.V_OBJECT {
  552. return ast.Node{}, false
  553. }
  554. next := current.Get(k)
  555. if !next.Valid() {
  556. return ast.Node{}, false
  557. }
  558. current = next
  559. }
  560. return *current, true
  561. }
  562. // createASTNode creates an AST node from a value
  563. func (p *Plugin) createASTNode(value any) ast.Node {
  564. if value == nil {
  565. return ast.NewNull()
  566. }
  567. switch v := value.(type) {
  568. case string:
  569. return ast.NewString(v)
  570. case int:
  571. return ast.NewNumber(strconv.Itoa(v))
  572. case int64:
  573. return ast.NewNumber(strconv.FormatInt(v, 10))
  574. case float64:
  575. return ast.NewNumber(strconv.FormatFloat(v, 'f', -1, 64))
  576. case bool:
  577. return ast.NewBool(v)
  578. default:
  579. // Try to marshal and parse
  580. if bytes, err := sonic.Marshal(v); err == nil {
  581. if node, err := sonic.Get(bytes); err == nil {
  582. return node
  583. }
  584. }
  585. return ast.Node{}
  586. }
  587. }
  588. func ToFloat64(v any) (float64, error) {
  589. switch val := v.(type) {
  590. case float64:
  591. return val, nil
  592. case float32:
  593. return float64(val), nil
  594. case int:
  595. return float64(val), nil
  596. case int32:
  597. return float64(val), nil
  598. case int64:
  599. return float64(val), nil
  600. case string:
  601. return strconv.ParseFloat(val, 64)
  602. default:
  603. return 0, fmt.Errorf("cannot convert %T to float64", v)
  604. }
  605. }
  606. // compareNumeric compares two numeric values
  607. func (p *Plugin) compareNumeric(actualValue any, expectedValue, operator string) bool {
  608. actualFloat, err := ToFloat64(actualValue)
  609. if err != nil {
  610. return false
  611. }
  612. expectedFloat, err := strconv.ParseFloat(expectedValue, 64)
  613. if err != nil {
  614. return false
  615. }
  616. switch operator {
  617. case ">":
  618. return actualFloat > expectedFloat
  619. case "<":
  620. return actualFloat < expectedFloat
  621. case ">=":
  622. return actualFloat >= expectedFloat
  623. case "<=":
  624. return actualFloat <= expectedFloat
  625. default:
  626. return false
  627. }
  628. }
  629. // stringInSlice checks if a string is in a slice
  630. func (p *Plugin) stringInSlice(str string, slice []string) bool {
  631. return slices.Contains(slice, str)
  632. }
  633. // applyLazyPatches applies patches queued in meta from other plugins
  634. func (p *Plugin) applyLazyPatches(root *ast.Node, meta *meta.Meta) bool {
  635. lazyPatches := GetLazyPatches(meta)
  636. if len(lazyPatches) == 0 {
  637. return false
  638. }
  639. modified := false
  640. for _, lazyPatch := range lazyPatches {
  641. if opModified, err := p.applyOperation(&lazyPatch, root); err == nil && opModified {
  642. modified = true
  643. }
  644. }
  645. return modified
  646. }
  647. // resolvePlaceholdersAST replaces placeholders in values with actual values from JSON data using AST
  648. func (p *Plugin) resolvePlaceholdersAST(value any, root *ast.Node) any {
  649. if strValue, ok := value.(string); ok {
  650. // Check if it's a placeholder pattern {{key}}
  651. if strings.HasPrefix(strValue, "{{") && strings.HasSuffix(strValue, "}}") {
  652. placeholderKey := strValue[2 : len(strValue)-2]
  653. if actualValue := p.getNestedValueAST(root, placeholderKey); actualValue != nil {
  654. return actualValue
  655. }
  656. }
  657. }
  658. return value
  659. }