/**
* 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);
}
}