001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client;
017
018import java.net.URI;
019import java.util.Objects;
020import java.util.function.Function;
021
022public class HttpConfig {
023    private static final String
024            DEFAULT_API_BASE_URI = "https://api.nexmo.com",
025            DEFAULT_REST_BASE_URI = "https://rest.nexmo.com",
026            DEFAULT_API_EU_BASE_URI = "https://api-eu.vonage.com",
027            DEFAULT_VIDEO_BASE_URI = "https://video.api.vonage.com";
028
029    private final int timeoutMillis;
030    private final String customUserAgent, apiBaseUri, restBaseUri, apiEuBaseUri, videoBaseUri;
031    private final Function<ApiRegion, String> regionalUriGetter;
032    private final URI proxy;
033
034    private HttpConfig(Builder builder) {
035        if ((timeoutMillis = builder.timeoutMillis) < 10) {
036            throw new IllegalArgumentException("Timeout must be greater than 10ms.");
037        }
038        proxy = builder.proxy;
039        apiBaseUri = builder.apiBaseUri;
040        restBaseUri = builder.restBaseUri;
041        videoBaseUri = builder.videoBaseUri;
042        apiEuBaseUri = builder.apiEuBaseUri;
043        regionalUriGetter = builder.regionalUriGetter;
044        customUserAgent = builder.customUserAgent;
045    }
046
047    /**
048     * Gets the timeout setting for the underlying HTTP client configuration.
049     *
050     * @return The request timeout in milliseconds.
051     * @since 7.8.0
052     */
053    public int getTimeoutMillis() {
054        return timeoutMillis;
055    }
056
057    public String getApiBaseUri() {
058        return apiBaseUri;
059    }
060
061    public String getRestBaseUri() {
062        return restBaseUri;
063    }
064
065    public String getVideoBaseUri() {
066        return videoBaseUri;
067    }
068
069    public String getApiEuBaseUri() {
070        return apiEuBaseUri;
071    }
072
073    /**
074     * Returns the base URI for the specified region.
075     *
076     * @param region The region as an enum.
077     * @return The base URI for the given region.
078     * @since 8.11.0
079     */
080    public URI getRegionalBaseUri(ApiRegion region) {
081        return URI.create(regionalUriGetter.apply(region));
082    }
083
084    /**
085     * Returns the custom user agent string that will be appended to the default one, if set.
086     *
087     * @return The custom user agent string to append, or {@code null} if not set.
088     * @since 8.11.0
089     */
090    public String getCustomUserAgent() {
091        return customUserAgent;
092    }
093
094    /**
095     * Returns the proxy URL to use for the underlying HTTP client configuration.
096     *
097     * @return The proxy URI, or {@code null} if not set.
098     * @since 8.15.0
099     */
100    public URI getProxy() {
101        return proxy;
102    }
103
104    @Deprecated
105    public boolean isDefaultApiBaseUri() {
106        return DEFAULT_API_BASE_URI.equals(apiBaseUri);
107    }
108
109    @Deprecated
110    public boolean isDefaultRestBaseUri() {
111        return DEFAULT_REST_BASE_URI.equals(restBaseUri);
112    }
113
114    @Deprecated
115    public boolean isDefaultApiEuBaseUri() {
116        return DEFAULT_API_EU_BASE_URI.equals(apiEuBaseUri);
117    }
118
119    @Deprecated
120    public boolean isDefaultVideoBaseUri() {
121        return DEFAULT_VIDEO_BASE_URI.equals(videoBaseUri);
122    }
123
124    @Deprecated
125    public String getVersionedApiBaseUri(String version) {
126        return appendVersionToUri(apiBaseUri, version);
127    }
128
129    @Deprecated
130    public String getVersionedRestBaseUri(String version) {
131        return appendVersionToUri(restBaseUri, version);
132    }
133
134    @Deprecated
135    public String getVersionedApiEuBaseUri(String version) {
136        return appendVersionToUri(apiEuBaseUri, version);
137    }
138
139    @Deprecated
140    public String getVersionedVideoBaseUri(String version) {
141        return appendVersionToUri(videoBaseUri, version);
142    }
143
144    private String appendVersionToUri(String uri, String version) {
145        return uri + "/" + version;
146    }
147
148    /**
149     * Creates a standard HttpConfig.
150     *
151     * @return an HttpConfig object with sensible defaults.
152     */
153    public static HttpConfig defaultConfig() {
154        return builder().build();
155    }
156
157    /**
158     * Entrypoint for creating a custom HttpConfig.
159     *
160     * @return A new Builder.
161     */
162    public static Builder builder() {
163        return new Builder();
164    }
165
166    /**
167     * Builder for configuring the base URI and timeout of the client.
168     */
169    public static class Builder {
170        private int timeoutMillis = 60_000;
171        private URI proxy;
172        private Function<ApiRegion, String> regionalUriGetter = region -> "https://"+region+".vonage.com";
173        private String customUserAgent,
174                apiBaseUri = DEFAULT_API_BASE_URI,
175                restBaseUri = DEFAULT_REST_BASE_URI,
176                apiEuBaseUri = DEFAULT_API_EU_BASE_URI,
177                videoBaseUri = DEFAULT_VIDEO_BASE_URI;
178
179        /**
180         * Constructor.
181         *
182         * @deprecated Will be made private in the next major version.
183         */
184        @Deprecated
185        public Builder() {}
186
187        private String sanitizeUri(String uri) {
188            if (uri != null && uri.endsWith("/")) {
189                return uri.substring(0, uri.length() - 1);
190            }
191            return uri;
192        }
193
194        /**
195         * Sets the socket timeout for requests. By default, this is one minute (60000 ms).
196         * <br>
197         * Note that this timeout applies to both the connection and socket; therefore, it defines
198         * the maximum time for each stage of the request. For example, if set to 30 seconds, then
199         * establishing a connection may take 29 seconds and receiving a response may take 29 seconds
200         * without timing out (therefore a total of 58 seconds for the request).
201         *
202         * @param timeoutMillis The timeout in milliseconds.
203         *
204         * @return This builder.
205         * @since 7.8.0
206         */
207        public Builder timeoutMillis(int timeoutMillis) {
208            this.timeoutMillis = timeoutMillis;
209            return this;
210        }
211
212        /**
213         * Sets the proxy to use for requests. This will route requests through the specified URL.
214         *
215         * @param proxy The proxy URI to use as a string.
216         * @return This builder.
217         * @since 8.15.0
218         * @throws IllegalArgumentException If the proxy URI is invalid.
219         */
220        public Builder proxy(String proxy) {
221            return proxy(URI.create(proxy));
222        }
223
224        /**
225         * Sets the proxy to use for requests. This will route requests through the specified URL.
226         *
227         * @param proxy The proxy URI to use.
228         * @return This builder.
229         * @since 8.15.0
230         */
231        public Builder proxy(URI proxy) {
232            this.proxy = proxy;
233            return this;
234        }
235
236        /**
237         * Replaces the URI used in "api" endpoints.
238         *
239         * @param apiBaseUri The base uri to use.
240         * @return This builder.
241         */
242        public Builder apiBaseUri(String apiBaseUri) {
243            this.apiBaseUri = sanitizeUri(apiBaseUri);
244            return this;
245        }
246
247        /**
248         * Replaces the base URI used in "rest" endpoints.
249         *
250         * @param restBaseUri The base uri to use.
251         * @return This builder.
252         */
253        public Builder restBaseUri(String restBaseUri) {
254            this.restBaseUri = sanitizeUri(restBaseUri);
255            return this;
256        }
257
258        /**
259         * Replaces the base URI used in "api-eu" endpoints.
260         *
261         * @param apiEuBaseUri The base URI to use.
262         * @return This builder.
263         */
264        public Builder apiEuBaseUri(String apiEuBaseUri) {
265            this.apiEuBaseUri = sanitizeUri(apiEuBaseUri);
266            return this;
267        }
268
269        /**
270         * Replaces the base URI used in "video" endpoints.
271         *
272         * @param videoBaseUri The base URI to use.
273         * @return This builder.
274         * @since 8.0.0
275         */
276        public Builder videoBaseUri(String videoBaseUri) {
277            this.videoBaseUri = sanitizeUri(videoBaseUri);
278            return this;
279        }
280
281        /**
282         * Replaces the base URI used in all requests with the specified parameter.
283         *
284         * @param baseUri The base URI to use.
285         * @return This builder.
286         */
287        public Builder baseUri(String baseUri) {
288            String sanitizedUri = sanitizeUri(baseUri);
289            regionalUriGetter(region -> sanitizedUri.replace("://", "://" + region + '.'));
290            apiBaseUri = sanitizedUri;
291            restBaseUri = sanitizedUri;
292            apiEuBaseUri = sanitizedUri;
293            videoBaseUri = sanitizedUri;
294            return this;
295        }
296
297        /**
298         * Replaces the base URI used in all requests with the specified parameter.
299         *
300         * @param baseUri The base URI to use.
301         * @return This builder.
302         * @since 8.9.0
303         */
304        public Builder baseUri(URI baseUri) {
305            return baseUri(baseUri.toString());
306        }
307
308        /**
309         * Sets a function to get the base URI for a given region.
310         *
311         * @param uriGetter The function which takes as input a region and returns a base URI as a string.
312         * @return This builder.
313         * @since 8.11.0
314         */
315        public Builder regionalUriGetter(Function<ApiRegion, String> uriGetter) {
316            this.regionalUriGetter = Objects.requireNonNull(uriGetter);
317            return this;
318        }
319
320        /**
321         * Appends a custom string to the default {@code User-Agent} header. This is mainly used for
322         * derivatives of the SDK, or to distinguish particular users / use cases.
323         *
324         * @param userAgent The user agent string to append to the existing one. Must be less than 128 characters.
325         * @return This builder.
326         * @since 8.11.0
327         */
328        public Builder appendUserAgent(String userAgent) {
329            if ((this.customUserAgent = Objects.requireNonNull(userAgent).trim()).length() > 127) {
330                throw new IllegalArgumentException("User agent string must be less than 128 characters in length.");
331            }
332            if (customUserAgent.isEmpty()) {
333                throw new IllegalArgumentException("Custom user agent string cannot be blank.");
334            }
335            return this;
336        }
337
338        /**
339         * Builds the HttpConfig.
340         *
341         * @return A new HttpConfig object from the stored builder options.
342         */
343        public HttpConfig build() {
344            return new HttpConfig(this);
345        }
346    }
347}