ApiUrlDiscoveryHelper.java
package io.github.evisentin.wordpress.rest.client.adapter.okhttp.discovery;
import io.github.evisentin.wordpress.rest.client.domain.exception.ApiUrlNotFoundException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.SneakyThrows;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.Strings;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Internal utility for discovering the WordPress REST API base URL.
*
* <p>This helper implements the WordPress REST API discovery mechanism by issuing an HTTP {@code HEAD} request against
* a site URL and inspecting {@code Link} response headers for the standard WordPress API relation
* {@code https://api.w.org/}.</p>
*
* <p>When a matching relation is found, the corresponding API endpoint URL is extracted and returned. Trailing slashes
* are removed to ensure a consistent base URL format.</p>
*
* <p>This class is intended for internal use by the client implementation and is not part of the public API. Consumers
* should rely on higher-level client abstractions rather than invoking this utility directly.</p>
*
* <p>The implementation follows the WordPress REST API discovery process described by WordPress and compatible API
* providers.</p>
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ApiUrlDiscoveryHelper {
private static final Pattern LINK_PATTERN = Pattern.compile("<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"");
/**
* Discovers the WordPress REST API base URL for a site.
*
* <p>An HTTP {@code HEAD} request is sent to the provided site URL and the returned {@code Link} headers are
* inspected for a relation of {@code https://api.w.org/}. The associated URL is returned as the API endpoint.</p>
*
* @param httpClient
* the OkHttp client used to perform the discovery request; must not be {@code null}
* @param baseUrl
* the WordPress site URL from which discovery should start; must not be {@code null}
*
* @return the discovered WordPress REST API base URL
*
* @throws ApiUrlNotFoundException
* if no WordPress REST API discovery link is present in the response
*/
@SneakyThrows
public static String resolveApiUrl(final @NonNull OkHttpClient httpClient,
final @NonNull String baseUrl) {
final Request request = new Request.Builder()
.url(baseUrl)
.head()
.build();
try (Response response = httpClient.newCall(request).execute()) {
final Headers headers = response.headers();
for (String linkHeader : headers.values("Link")) {
final Matcher matcher = LINK_PATTERN.matcher(linkHeader);
while (matcher.find()) {
final String url = matcher.group(1);
final String rel = matcher.group(2);
if ("https://api.w.org/".equals(rel)) {
return Strings.CI.removeEnd(url, "/");
}
}
}
}
throw new ApiUrlNotFoundException(baseUrl);
}
}