1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.adservices.service.js;
18 
19 import static java.util.Arrays.asList;
20 
21 import android.adservices.common.AdSelectionSignals;
22 
23 import com.google.common.collect.ImmutableList;
24 
25 import org.json.JSONArray;
26 import org.json.JSONException;
27 import org.json.JSONObject;
28 
29 import java.util.List;
30 import java.util.Map;
31 import java.util.stream.Collectors;
32 
33 /** Represent an argument to supply to an JS script. */
34 public abstract class JSScriptArgument {
35     protected final String mName;
36 
JSScriptArgument(String name)37     protected JSScriptArgument(String name) {
38         mName = name;
39     }
40 
41     /**
42      * @return an argument with the given {@code name} and the given string value {@code value}
43      */
stringArg(String name, String value)44     public static JSScriptStringArgument stringArg(String name, String value) {
45         return new JSScriptStringArgument(name, value);
46     }
47 
48     /**
49      * @return a JS object with the given {@code name} and value obtained parsing the given {@code
50      *     value}.
51      * @throws JSONException if {@code value} doesn't represent a valid JSON object
52      */
jsonArg(String name, String value)53     public static JSScriptJsonArgument jsonArg(String name, String value) throws JSONException {
54         // Creating the JSONObject just to parse value and cause a JSONException if invalid.
55         new JSONObject(value);
56         return new JSScriptJsonArgument(name, value);
57     }
58 
59     /**
60      * @return a JS array object with the given {@code name} and value obtained parsing the given
61      *     {@code value}.
62      * @throws JSONException if {@code value} doesn't represent a valid JSON array object
63      */
jsonArrayArg(String name, String value)64     public static JSScriptJsonArrayArgument jsonArrayArg(String name, String value)
65             throws JSONException {
66         new JSONArray(value);
67         return new JSScriptJsonArrayArgument(name, value);
68     }
69 
70     /**
71      * @return a JS object with the given {@code name} and value obtained parsing the given {@code
72      *     value}.
73      * @throws JSONException if {@code value} doesn't represent a valid JSON object
74      */
jsonArg(String name, AdSelectionSignals value)75     public static JSScriptJsonArgument jsonArg(String name, AdSelectionSignals value)
76             throws JSONException {
77         // TODO(b/238849930) Merge this validation with AdSelectionSignals validation
78         new JSONObject(value.toString());
79         return new JSScriptJsonArgument(name, value.toString());
80     }
81 
82     /**
83      * @return a JS object with the given {@code name} and value obtained parsing the given map
84      *     {@code value}.
85      * @throws JSONException if {@code value} doesn't represent a valid JSON object
86      */
stringMapToRecordArg(String name, Map<String, String> stringMap)87     public static JSScriptArgument stringMapToRecordArg(String name, Map<String, String> stringMap)
88             throws JSONException {
89         ImmutableList.Builder<JSScriptArgument> mapArg = ImmutableList.builder();
90         for (Map.Entry<String, String> signal : stringMap.entrySet()) {
91             mapArg.add(jsonArg(signal.getKey(), signal.getValue()));
92         }
93         return recordArg(name, mapArg.build());
94     }
95 
96     /**
97      * @return a JS array argument with the given {@code name} initialized with the values specified
98      *     with {@code items}.
99      */
arrayArg( String name, T... items)100     public static <T extends JSScriptArgument> JSScriptArrayArgument<T> arrayArg(
101             String name, T... items) {
102         return new JSScriptArrayArgument<>(name, asList(items));
103     }
104 
105     /**
106      * @return a JS array argument with the given {@code name} initialized with the values specified
107      *     with {@code items}.
108      */
arrayArg( String name, List<T> items)109     public static <T extends JSScriptArgument> JSScriptArrayArgument<T> arrayArg(
110             String name, List<T> items) {
111         return new JSScriptArrayArgument<>(name, items);
112     }
113 
114     /**
115      * @return a JS array argument with the given {@code name} initialized with the values specified
116      *     with {@code items}.
117      */
stringArrayArg( String name, List<String> items)118     public static JSScriptArrayArgument<JSScriptStringArgument> stringArrayArg(
119             String name, List<String> items) {
120         return new JSScriptArrayArgument<>(
121                 name,
122                 items.stream().map(str -> stringArg("ignored", str)).collect(Collectors.toList()));
123     }
124 
125     /**
126      * @return a JS array argument with the given {@code name} initialized with the values specified
127      *     with {@code items}
128      */
129     public static <T extends Number>
numericArrayArg( String name, List<T> items)130             JSScriptArrayArgument<JSScriptNumericArgument<T>> numericArrayArg(
131                     String name, List<T> items) {
132         return new JSScriptArrayArgument<>(
133                 name,
134                 items.stream()
135                         .map(num -> new JSScriptNumericArgument<>("ignored", num))
136                         .collect(Collectors.toList()));
137     }
138 
139     /**
140      * @return a JS object with the given {@code name} and {@code fields} as fields values.
141      */
recordArg(String name, JSScriptArgument... fields)142     public static JSScriptRecordArgument recordArg(String name, JSScriptArgument... fields) {
143         return new JSScriptRecordArgument(name, asList(fields));
144     }
145 
146     /**
147      * @return a JS object with the given {@code name} and {@code fields} as fields values.
148      */
recordArg(String name, List<JSScriptArgument> fields)149     public static JSScriptRecordArgument recordArg(String name, List<JSScriptArgument> fields) {
150         return new JSScriptRecordArgument(name, fields);
151     }
152 
153     /**
154      * @return a numeric variable with the given {@code name} and {@code value}.
155      */
numericArg(String name, T value)156     public static <T extends Number> JSScriptNumericArgument<T> numericArg(String name, T value) {
157         return new JSScriptNumericArgument<>(name, value);
158     }
159 
160     /**
161      * @return the JS code to use to initialize the variable.
162      */
variableDeclaration()163     public String variableDeclaration() {
164         return String.format("const %s = %s;", name(), initializationValue());
165     }
166 
167     /**
168      * @return name of the argument as referred in the call to the auction script function.
169      */
name()170     public String name() {
171         return mName;
172     }
173 
174     /**
175      * @return the JS code to use to initialize the newly declared variable.
176      */
initializationValue()177     abstract String initializationValue();
178 }
179