/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jooby; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.function.Function; import org.jooby.internal.parser.BeanParser; import com.google.inject.Key; import com.google.inject.TypeLiteral; /** * Parse a request param (path, query, form) or body to something else. * * <h1>Registering a parser</h1> * <p> * There are two ways of registering a parser: * </p> * * <ol> * <li>Using the {@link Jooby#parser(Parser)} method</li> * <li>From a Guice module: * * <pre> * Multibinder<Parser> pcb = Multibinder .newSetBinder(binder, Parser.class); pcb.addBinding().to(MyParser.class); * </pre> * </li> * </ol> * Parsers are executed in the order they were registered. The first converter that resolved the * type: wins!. * * <h2>Built-in parsers</h2> * <p> * These are the built-in parsers: * </p> * <ol> * <li>Primitives and String: convert to int, double, char, string, etc...</li> * <li>Enums (case-sensitive)</li> * <li>{@link java.util.Date}: It parses a date using the <code>application.dateFormat</code> * property.</li> * <li>{@link java.time.LocalDate}: It parses a date using the <code>application.dateFormat</code> * property.</li> * <li>{@link java.util.Locale}</li> * <li>Classes with a static method: <code>valueOf</code></li> * <li>Classes with a static method: <code>fromName</code></li> * <li>Classes with a static method: <code>fromString</code></li> * <li>Classes with a public constructor with one <code>String</code> argument</li> * <li>It is an Optional<T>, List<T>, Set<T> or SortedSet<T> where T * satisfies one of previous rules</li> * </ol> * * @author edgar * @see Jooby#parser(Parser) * @since 0.6.0 */ public interface Parser { /** * A parser callback. * * @author edgar * * @param <T> Type of data to parse. * @since 0.6.0 */ interface Callback<T> { /** * Parse a raw value to something else. * * @param data Data to parse. * @return A parsed value * @throws Exception If something goes wrong. */ Object invoke(T data) throws Throwable; } /** * Expose HTTP params from path, query, form url encoded or multipart request as a raw string. * * @author edgar * @since 0.6.0 */ interface ParamReference<T> extends Iterable<T> { /** * @return Descriptive type: parameter, header, cookie, etc... */ String type(); /** * @return Parameter name. */ String name(); /** * @return Return the first param or throw {@link Err} with a bad request code when missing. */ T first(); /** * @return Return the last param or throw {@link Err} with a bad request code when missing. */ T last(); /** * Get the param at the given index or throw {@link Err} with a bad request code when missing. * * @param index Param index. * @return Param at the given index or throw {@link Err} with a bad request code when missing. */ T get(int index); @Override Iterator<T> iterator(); /** * @return Number of values for this parameter. */ int size(); } /** * Expose the HTTP body as a series of bytes or text. * * @author edgar * @since 0.6.0 */ interface BodyReference { /** * Returns the HTTP body as a byte array. * * @return HTTP body as byte array. * @throws IOException If reading fails. */ byte[] bytes() throws IOException; /** * Returns the HTTP body as text. * * @return HTTP body as text. * @throws IOException If reading fails. */ String text() throws IOException; /** * @return Body length. */ long length(); /** * Write the content to the given output stream. This method won't close the * {@link OutputStream}. * * @param output An output stream. * @throws Exception If write fails. */ void writeTo(final OutputStream output) throws Exception; } /** * A parser can be executed against a simply HTTP param, a set of HTTP params, an file * {@link Upload} or HTTP {@link BodyReference}. * * This class provides utility methods for selecting one of the previous source. It is possible to * write a parser and apply it against multiple sources, like HTTP param and HTTP body. * * Here is an example that will parse text to an int, provided as a HTTP param or body: * * <pre> * { * parser((type, ctx) {@literal ->} { * if (type.getRawType() == int.class) { * return ctx * .param(values {@literal ->} Integer.parseInt(values.get(0)) * .body(body {@literal ->} Integer.parseInt(body.text())); * } * return ctx.next(); * }); * * get("/", req {@literal ->} { * // use the param strategy * return req.param("p").intValue(); * }); * * post("/", req {@literal ->} { * // use the body strategy * return req.body().intValue(); * }); * } * </pre> * * @author edgar * @since 0.6.0 */ interface Builder { /** * Add a HTTP body callback. The Callback will be executed when current context is bound to the * HTTP body via {@link Request#body()}. * * If current {@link Context} isn't a HTTP body a call to {@link Context#next()} is made. * * @param callback A body parser callback. * @return This builder. */ Builder body(Callback<BodyReference> callback); /** * Like {@link #body(Callback)} but it skip the callback if the requested type is an * {@link Optional}. * * @param callback A body parser callback. * @return This builder. */ Builder ifbody(Callback<BodyReference> callback); /** * Add a HTTP param callback. The Callback will be executed when current context is bound to a * HTTP param via {@link Request#param(String)}. * * If current {@link Context} isn't a HTTP param a call to {@link Context#next()} is made. * * @param callback A param parser callback. * @return This builder. */ Builder param(Callback<ParamReference<String>> callback); /** * Like {@link #param(Callback)} but it skip the callback if the requested type is an * {@link Optional}. * * @param callback A param parser callback. * @return This builder. */ Builder ifparam(Callback<ParamReference<String>> callback); /** * Add a HTTP params callback. The Callback will be executed when current context is bound to a * HTTP params via {@link Request#params()}. * * If current {@link Context} isn't a HTTP params a call to {@link Context#next()} is made. * * @param callback A params parser callback. * @return This builder. */ Builder params(Callback<Map<String, Mutant>> callback); /** * Like {@link #params(Callback)} but it skip the callback if the requested type is an * {@link Optional}. * * @param callback A params parser callback. * @return This builder. */ Builder ifparams(Callback<Map<String, Mutant>> callback); /** * Add a HTTP upload callback. The Callback will be executed when current context is bound to a * HTTP upload via {@link Request#param(String)}. * * If current {@link Context} isn't a HTTP upload a call to {@link Context#next()} is made. * * @param callback A upload parser callback. * @return This builder. */ Builder upload(Callback<ParamReference<Upload>> callback); /** * Like {@link #upload(Callback)} but it skip the callback if the requested type is an * {@link Optional}. * * @param callback A upload parser callback. * @return This builder. */ Builder ifupload(Callback<ParamReference<Upload>> callback); } /** * Allows you to access to parsing strategies, content type view {@link #type()} and invoke next * parser in the chain via {@link #next()} methods. * * @author edgar * @since 0.6.0 */ interface Context extends Builder { /** * Requires a service with the given type. * * @param type Service type. * @param <T> Service type. * @return A service. */ <T> T require(final Class<T> type); /** * Requires a service with the given type. * * @param type Service type. * @param <T> Service type. * @return A service. */ <T> T require(final TypeLiteral<T> type); /** * Requires a service with the given type. * * @param key Service key. * @param <T> Service type. * @return A service. */ <T> T require(final Key<T> key); /** * Content Type header, if current context was bind to a HTTP body via {@link Request#body()}. * If current context was bind to a HTTP param, media type is set to <code>text/plain</code>. * * @return Current type. */ MediaType type(); /** * Invoke next parser in the chain. * * @return A parsed value. * @throws Exception An err with a 400 status. */ Object next() throws Throwable; /** * Invoke next parser in the chain and switch/change the target type we are looking for. Useful * for generic containers classes, like collections or optional values. * * @param type A new type to use. * @return A parsed value. * @throws Exception An err with a 400 status. */ Object next(TypeLiteral<?> type) throws Throwable; /** * Invoke next parser in the chain and switch/change the target type we are looking for but also * the current value. Useful for generic containers classes, like collections or optional * values. * * @param type A new type to use. * @param data Data to be parsed. * @return A parsed value. * @throws Exception An err with a 400 status. */ Object next(TypeLiteral<?> type, Object data) throws Throwable; } /** Utility function to handle empty values as {@link NoSuchElementException}. */ static Function<String, String> NOT_EMPTY = v -> { if (v.length() == 0) { throw new NoSuchElementException(); } return v; }; /** * <p> * Parse one or more values to the required type. If the parser doesn't support the required type * a call to {@link Context#next(TypeLiteral, Object)} must be done. * </p> * * Example: * * <pre> * Parser converter = (type, ctx) {@literal ->} { * if (type.getRawType() == MyType.class) { * // convert to MyType * return ctx.param(values {@literal ->} new MyType(values.get(0))); * } * // no luck! move next * return next.next(); * } * </pre> * * It's also possible to create generic/parameterized types too: * * <pre> * public class MyContainerType<T> {} * * ParamConverter converter = (type, ctx) {@literal ->} { * if (type.getRawType() == MyContainerType.class) { * // Creates a new type from current generic type * TypeLiterale<?> paramType = TypeLiteral * .get(((ParameterizedType) toType.getType()).getActualTypeArguments()[0]); * * // Ask param converter to resolve the new/next type. * Object result = next.next(paramType); * return new MyType(result); * } * // no luck! move next * return ctx.next(); * } * </pre> * * @param type Requested type. * @param ctx Execution context. * @return A parsed value. * @throws Throwable If conversion fails. */ Object parse(TypeLiteral<?> type, Context ctx) throws Throwable; /** * Overwrite the default bean parser with <code>empty/null</code> supports. The default bean * parser doesn't allow <code>null</code>, so if a parameter is optional you must declare it as * {@link Optional} otherwise parsing fails with a <code>404/500</code> status code. * * For example: * <pre>{@code * * public class Book { * * public String title; * * public Date releaseDate; * * public String toString() { * return title + ":" + releaseDate; * } * } * * { * parser(Parser.bean(true)); * * post("/", req -> { * return req.params(Book.class).toString(); * }); * } * }</pre> * * <p> * With <code>/?title=Title&releaseDate=</code> prints <code>Title:null</code>. * </p> * <p> * Now, same call with <code>lenient=false</code> results in <code>Bad Request: 400</code> * because <code>releaseDate</code> if required and isn't present in the HTTP request. * </p> * * <p> * This feature is useful while submitting forms. * </p> * * @param lenient Enabled null/empty supports while parsing HTTP params as Java Beans. * @return A new bean parser. */ static Parser bean(final boolean lenient) { return new BeanParser(lenient); } }