1+ package com .syncapi .service .substitution ;
2+
3+ import com .syncapi .dto .request .SubstitutedRequest ;
4+ import org .springframework .stereotype .Service ;
5+
6+ import java .util .HashMap ;
7+ import java .util .Map ;
8+
9+ /**
10+ * Service for variable substitution in strings and requests.
11+ */
12+ @ Service
13+ public class VariableSubstitutionService {
14+ /**
15+ * Substitutes variables in the input string based on the provided variables.
16+ * <p>
17+ * For example, substitute("Hello, {{name}}!", Map.of("name", "World")) returns "Hello, World!"
18+ * <p>
19+ * Variables not found in the map are left unchanged.
20+ *
21+ * @param input the input string containing {{key}} patterns
22+ * @param variables the map of variable keys and their corresponding values
23+ * @return the input string with variables substituted
24+ */
25+ public String substitute (String input , Map <String , String > variables ) {
26+ if (input == null || input .isEmpty ()) {
27+ return input ;
28+ }
29+
30+ if (variables == null || variables .isEmpty ()) {
31+ return input ;
32+ }
33+
34+ String result = input ;
35+ for (Map .Entry <String , String > entry : variables .entrySet ()) {
36+ String token = "{{" + entry .getKey () + "}}" ;
37+
38+ String value = entry .getValue ();
39+ if (value != null ) {
40+ result = result .replace (token , value );
41+ }
42+ }
43+
44+ return result ;
45+ }
46+
47+ /**
48+ * Substitutes variables in the Request's URL, headers, and body.
49+ * <p>
50+ * Returns a new SubstitutedRequest DTO to avoid mutating the JPA entity.
51+ *
52+ * @param url the request URL
53+ * @param headers the request headers
54+ * @param body the request body
55+ * @param variables the map of variable keys and their corresponding values
56+ * @return a new SubstitutedRequest with variables substituted
57+ */
58+ public SubstitutedRequest substituteRequest (String url , Map <String , String > headers , Map <String , Object > body ,
59+ Map <String , String > variables ) {
60+ String substitutedUrl = substitute (url , variables );
61+ Map <String , String > substitutedHeaders = substituteHeaders (headers , variables );
62+ Map <String , Object > substitutedBody = substituteBody (body , variables );
63+
64+ return new SubstitutedRequest (substitutedUrl , substitutedHeaders , substitutedBody );
65+ }
66+
67+ /**
68+ * Substitutes variables in header values.
69+ *
70+ * @param headers the request headers
71+ * @param variables the map of variable keys and their corresponding values
72+ * @return a new map with substituted header values
73+ */
74+ private Map <String , String > substituteHeaders (Map <String , String > headers , Map <String , String > variables ) {
75+ if (headers == null || headers .isEmpty ()) {
76+ return headers ;
77+ }
78+
79+ Map <String , String > substitutedHeaders = new HashMap <>();
80+ for (Map .Entry <String , String > entry : headers .entrySet ()) {
81+ String substitutedValue = substitute (entry .getValue (), variables );
82+ substitutedHeaders .put (entry .getKey (), substitutedValue );
83+ }
84+
85+ return substitutedHeaders ;
86+ }
87+
88+ /**
89+ * Substitutes variables in body values (only for String values).
90+ *
91+ * @param body the request body
92+ * @param variables the map of variable keys and their corresponding values
93+ * @return a new map with substituted body values
94+ */
95+ private Map <String , Object > substituteBody (Map <String , Object > body , Map <String , String > variables ) {
96+ if (body == null || body .isEmpty ()) {
97+ return body ;
98+ }
99+
100+ Map <String , Object > substitutedBody = new HashMap <>();
101+ for (Map .Entry <String , Object > entry : body .entrySet ()) {
102+ Object value = entry .getValue ();
103+ if (value instanceof String ) {
104+ substitutedBody .put (entry .getKey (), substitute ((String ) value , variables ));
105+ } else {
106+ substitutedBody .put (entry .getKey (), value );
107+ }
108+ }
109+
110+ return substitutedBody ;
111+ }
112+ }
0 commit comments