// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.restapi; import ai.vespa.http.HttpURL; import com.fasterxml.jackson.databind.ObjectMapper; import com.yahoo.container.jdisc.AclMapping; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.RequestHandlerSpec; import com.yahoo.container.jdisc.RequestView; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.security.tls.Capability; import com.yahoo.security.tls.CapabilitySet; import com.yahoo.security.tls.ConnectionAuthContext; import javax.net.ssl.SSLSession; import java.io.InputStream; import java.security.Principal; import java.util.List; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalLong; /** * Rest API routing and response serialization * * @author bjorncs */ public interface RestApi { static Builder builder() { return new RestApiImpl.BuilderImpl(); } static RouteBuilder route(String pathPattern) { return new RestApiImpl.RouteBuilderImpl(pathPattern); } static HandlerConfigBuilder handlerConfig() { return new RestApiImpl.HandlerConfigBuilderImpl(); } HttpResponse handleRequest(HttpRequest request); ObjectMapper jacksonJsonMapper(); /** @see com.yahoo.container.jdisc.HttpRequestHandler#requestHandlerSpec() */ RequestHandlerSpec requestHandlerSpec(); /** @see com.yahoo.container.jdisc.utils.CapabilityRequiringRequestHandler */ CapabilitySet requiredCapabilities(RequestView req); interface Builder { Builder setObjectMapper(ObjectMapper mapper); Builder setDefaultRoute(RouteBuilder route); Builder addRoute(RouteBuilder route); Builder addFilter(Filter filter); /** see {@link RestApiMappers#DEFAULT_EXCEPTION_MAPPERS} for default mappers */ Builder addExceptionMapper(Class type, ExceptionMapper mapper); /** see {@link RestApiMappers#DEFAULT_RESPONSE_MAPPERS} for default mappers */ Builder addResponseMapper(Class type, ResponseMapper mapper); /** see {@link RestApiMappers#DEFAULT_REQUEST_MAPPERS} for default mappers */ Builder addRequestMapper(Class type, RequestMapper mapper); Builder registerJacksonResponseEntity(Class type); Builder registerJacksonRequestEntity(Class type); /** Disables mappers listed in {@link RestApiMappers#DEFAULT_EXCEPTION_MAPPERS} */ Builder disableDefaultExceptionMappers(); /** Disables mappers listed in {@link RestApiMappers#DEFAULT_RESPONSE_MAPPERS} */ Builder disableDefaultResponseMappers(); Builder disableDefaultAclMapping(); Builder requiredCapabilities(Capability... capabilities); Builder requiredCapabilities(CapabilitySet capabilities); RestApi build(); } interface RouteBuilder { RouteBuilder name(String name); RouteBuilder requiredCapabilities(Capability... capabilities); RouteBuilder requiredCapabilities(CapabilitySet capabilities); RouteBuilder addFilter(Filter filter); // GET RouteBuilder get(Handler handler); RouteBuilder get(Handler handler, HandlerConfigBuilder config); // POST RouteBuilder post(Handler handler); RouteBuilder post( Class type, HandlerWithRequestEntity handler); RouteBuilder post(Handler handler, HandlerConfigBuilder config); RouteBuilder post( Class type, HandlerWithRequestEntity handler, HandlerConfigBuilder config); // PUT RouteBuilder put(Handler handler); RouteBuilder put( Class type, HandlerWithRequestEntity handler); RouteBuilder put(Handler handler, HandlerConfigBuilder config); RouteBuilder put( Class type, HandlerWithRequestEntity handler, HandlerConfigBuilder config); // DELETE RouteBuilder delete(Handler handler); RouteBuilder delete(Handler handler, HandlerConfigBuilder config); // PATCH RouteBuilder patch(Handler handler); RouteBuilder patch( Class type, HandlerWithRequestEntity handler); RouteBuilder patch(Handler handler, HandlerConfigBuilder config); RouteBuilder patch( Class type, HandlerWithRequestEntity handler, HandlerConfigBuilder config); // Default RouteBuilder defaultHandler(Handler handler); RouteBuilder defaultHandler(Handler handler, HandlerConfigBuilder config); RouteBuilder defaultHandler( Class type, HandlerWithRequestEntity handler); RouteBuilder defaultHandler( Class type, HandlerWithRequestEntity handler, HandlerConfigBuilder config); } @FunctionalInterface interface Handler { RESPONSE_ENTITY handleRequest(RequestContext context) throws RestApiException; } @FunctionalInterface interface HandlerWithRequestEntity { RESPONSE_ENTITY handleRequest(RequestContext context, REQUEST_ENTITY requestEntity) throws RestApiException; } @FunctionalInterface interface ExceptionMapper { HttpResponse toResponse(RequestContext context, EXCEPTION exception); } @FunctionalInterface interface ResponseMapper { HttpResponse toHttpResponse(RequestContext context, RESPONSE_ENTITY responseEntity) throws RestApiException; } @FunctionalInterface interface RequestMapper { Optional toRequestEntity(RequestContext context) throws RestApiException; } @FunctionalInterface interface Filter { HttpResponse filterRequest(FilterContext context); } interface HandlerConfigBuilder { HandlerConfigBuilder withRequiredCapabilities(Capability... capabilities); HandlerConfigBuilder withRequiredCapabilities(CapabilitySet capabilities); HandlerConfigBuilder withReadAclAction(); HandlerConfigBuilder withWriteAclAction(); HandlerConfigBuilder withCustomAclAction(AclMapping.Action action); } interface RequestContext { HttpRequest request(); Method method(); PathParameters pathParameters(); QueryParameters queryParameters(); Headers headers(); Attributes attributes(); Optional requestContent(); RequestContent requestContentOrThrow(); ObjectMapper jacksonJsonMapper(); /** Scheme, domain and port, for the original request. Use this only for generating resources links, not for custom routing! */ // TODO: this needs to include path and query as well, to be useful for generating resource links that need not be rewritten. HttpURL baseRequestURL(); AclMapping.Action aclAction(); Optional userPrincipal(); Principal userPrincipalOrThrow(); Optional sslSession(); Optional connectionAuthContext(); interface Parameters { Optional getString(String name); String getStringOrThrow(String name); default Optional getBoolean(String name) { return getString(name).map(Boolean::valueOf);} default boolean getBooleanOrThrow(String name) { return Boolean.parseBoolean(getStringOrThrow(name)); } default OptionalLong getLong(String name) { return getString(name).map(Long::parseLong).map(OptionalLong::of).orElseGet(OptionalLong::empty); } default long getLongOrThrow(String name) { return Long.parseLong(getStringOrThrow(name)); } default OptionalDouble getDouble(String name) { return getString(name).map(Double::parseDouble).map(OptionalDouble::of).orElseGet(OptionalDouble::empty); } default double getDoubleOrThrow(String name) { return Double.parseDouble(getStringOrThrow(name)); } } interface PathParameters extends Parameters { HttpURL.Path getFullPath(); Optional getRest(); } interface QueryParameters extends Parameters { HttpURL.Query getFullQuery(); List getStringList(String name); } interface Headers extends Parameters {} interface Attributes { Optional get(String name); void set(String name, Object value); } interface RequestContent { String contentType(); InputStream content(); } } interface FilterContext { RequestContext requestContext(); String route(); HttpResponse executeNext(); } }