Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions docs/asciidoc/context.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ registry while the request it belongs to is being processed.

=== Parameters

There are several parameter types: `header`, `cookie`, `path`, `query`, `form` and `multipart`.
All them share an unified/type-safe API for accessing and manipulating their values.
There are several parameter types: `header`, `cookie`, `path`, `query`, `form`, `multipart`,
`session` and `flash`. All them share a unified/type-safe API for accessing and manipulating their values.

We are going to describe them briefly in the next sections, then go into specific features of the
<<Value API>>.

There is also a <<context-parameters-parameter-lookup,parameter lookup>> feature by which you can access a parameter from any combination
of the above types with well-defined priority.

==== Header

HTTP headers allow the client and the server to pass additional information with the request or the
Expand Down Expand Up @@ -568,6 +571,41 @@ Flash attributes are implemented using an `HTTP Cookie`. To customize the cookie
install(FlashScope(Cookie("myflash").setHttpOnly(true)))
----

==== Parameter Lookup

You can search for parameters in multiple sources with an explicitly defined priority using the
javadoc:Context[lookup] or javadoc:Context[lookup, java.lang.String, ParamSource...] method:

.Java
[source,java,role="primary"]
----
get("/{foo}", ctx -> {
String foo = ctx.lookup("foo", ParamSource.QUERY, ParamSource.PATH).value();
return "foo is: " + foo;
});

get("/{foo}", ctx -> {
String foo = ctx.lookup().inQuery().inPath().get("foo").value();
return "foo is: " + foo;
});
----

.Kotlin
[source,kotlin,role="secondary"]
----
get("/{foo}") { ctx ->
val foo = ctx.lookup("foo", ParamSource.QUERY, ParamSource.PATH).value()
"foo is: $foo"
}

get("/{foo}") { ctx ->
val foo = ctx.lookup().inQuery().inPath()["foo"].value()
"foo is: $foo"
}
----

In case of a request like `/bar?foo=baz`, `foo is: baz` will be returned since the query parameter
takes precedence over the path parameter.

include::value-api.adoc[]

Expand Down
33 changes: 33 additions & 0 deletions docs/asciidoc/mvc-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ MVC routes (Singleton vs Non-Singleton routes).

HTTP parameter provision is available via `*Param` annotations.

There is also a javadoc:annotations.Param[] annotation which allows to retrieve parameters from
<<mvc-api-parameters-multiple-sources,multiple sources>>.

==== Header

Provisioning of headers is available via javadoc:annotations.HeaderParam[] annotation:
Expand Down Expand Up @@ -600,6 +603,36 @@ class MyController {

<1> All context attributes must be set as arguments. They must be declared as `Map<String, Object>`

==== Multiple Sources

You can use the javadoc:annotations.Param[] annotation to search for a parameter in multiple sources.
The sources and their precedence can be specified as follows:

.Multiple Sources
[source, java, role = "primary"]
----
public class FooController {

@GET("/{foo}")
public String multipleSources(@Param({ QUERY, PATH }) String foo) {
return "foo is: " + foo;
}
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class FooController {

@GET("/{foo}")
fun multipleSources(@Param(QUERY, PATH) foo: String) = "foo is: $foo"
}
----

In case of a request like `/bar?foo=baz`, `foo is: baz` will be returned since the query parameter
takes precedence over the path parameter.

=== Responses

==== Status Code
Expand Down
51 changes: 51 additions & 0 deletions jooby/src/main/java/io/jooby/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
package io.jooby;

import io.jooby.internal.ParamLookupImpl;
import io.jooby.internal.ReadOnlyContext;
import io.jooby.internal.WebSocketSender;

Expand All @@ -22,6 +23,7 @@
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -715,6 +717,55 @@ public interface Context extends Registry {
*/
@Nonnull FileUpload file(@Nonnull String name);

/* **********************************************************************************************
* Parameter Lookup
* **********************************************************************************************
*/

/**
* Searches for a parameter in the specified sources, in the specified
* order, returning the first non-missing {@link Value}, or a 'missing'
* {@link Value} if none found.
* <p>
* At least one {@link ParamSource} must be specified.
*
* @param name The name of the parameter.
* @param sources Sources to search in.
* @return The first non-missing {@link Value} or a {@link Value} representing
* a missing value if none found.
* @throws IllegalArgumentException If no {@link ParamSource}s are specified.
*/
default Value lookup(String name, ParamSource... sources) {
if (sources.length == 0) {
throw new IllegalArgumentException("No parameter sources were specified.");
}

return Arrays.stream(sources)
.map(source -> source.provider.apply(this, name))
.filter(value -> !value.isMissing())
.findFirst()
.orElseGet(() -> Value.missing(name));
}

/**
* Returns a {@link ParamLookup} instance which is a fluent interface covering
* the functionality of the {@link #lookup(String, ParamSource...)} method.
*
* <pre>{@code
* Value foo = ctx.lookup()
* .inQuery()
* .inPath()
* .get("foo");
* }</pre>
*
* @return A {@link ParamLookup} instance.
* @see ParamLookup
* @see #lookup(String, ParamSource...)
*/
default ParamLookup lookup() {
return new ParamLookupImpl(this);
}

/* **********************************************************************************************
* Request Body
* **********************************************************************************************
Expand Down
146 changes: 146 additions & 0 deletions jooby/src/main/java/io/jooby/ParamLookup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby;

import java.util.Optional;

/**
* Fluent interface allowing to conveniently search context parameters
* in multiple sources.
*
* <pre>{@code
* Value foo = ctx.lookup()
* .inQuery()
* .inPath()
* .get("foo");
* }</pre>
*
* @see Context#lookup()
* @see Context#lookup(String, ParamSource...)
*/
public interface ParamLookup {

/**
* Adds the specified source to the search locations.
*
* @param source The source to add.
* @return This instance.
*/
Stage in(ParamSource source);

/**
* Adds the path parameters to the search locations.
*
* @return This instance.
*/
default Stage inPath() {
return in(ParamSource.PATH);
}

/**
* Adds the header parameters to the search locations.
*
* @return This instance.
*/
default Stage inHeader() {
return in(ParamSource.HEADER);
}

/**
* Adds the cookie parameters to the search locations.
*
* @return This instance.
*/
default Stage inCookie() {
return in(ParamSource.COOKIE);
}

/**
* Adds the flash parameters to the search locations.
*
* @return This instance.
*/
default Stage inFlash() {
return in(ParamSource.FLASH);
}

/**
* Adds the session parameters to the search locations.
*
* @return This instance.
*/
default Stage inSession() {
return in(ParamSource.SESSION);
}

/**
* Adds the query parameters to the search locations.
*
* @return This instance.
*/
default Stage inQuery() {
return in(ParamSource.QUERY);
}

/**
* Adds the form parameters to the search locations.
*
* @return This instance.
*/
default Stage inForm() {
return in(ParamSource.FORM);
}

/**
* Adds the multipart parameters to the search locations.
*
* @return This instance.
*/
default Stage inMultipart() {
return in(ParamSource.MULTIPART);
}

/**
* Fluent interface allowing to conveniently search context parameters
* in multiple sources.
*
* <pre>{@code
* Value foo = ctx.lookup()
* .inQuery()
* .inPath()
* .get("foo");
* }</pre>
*
* @see Context#lookup()
* @see Context#lookup(String, ParamSource...)
*/
interface Stage extends ParamLookup {

/**
* Searches for a parameter in the specified sources, in the specified
* order, returning the first non-missing {@link Value}, or a 'missing'
* {@link Value} if none found.
*
* @param name The name of the parameter.
* @return The first non-missing {@link Value} or a {@link Value} representing
* a missing value if none found.
*/
Value get(String name);

/**
* Wraps the result of {@link #get(String)} in an {@link Optional} if the
* value is a {@link ValueNode} or returns an empty {@link Optional}
* otherwise.
*
* @param name The name of the parameter.
* @return An {@link Optional} wrapping the result of {@link #get(String)}
*/
default Optional<ValueNode> getNode(String name) {
return Optional.of(get(name))
.map(v -> v instanceof ValueNode ? (ValueNode) v : null);
}
}
}
63 changes: 63 additions & 0 deletions jooby/src/main/java/io/jooby/ParamSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby;

import java.util.function.BiFunction;

/**
* List of possible parameter sources supported by {@link Context#lookup(String, ParamSource...)}.
*
* @see io.jooby.annotations.Param
* @see Context#lookup(String, ParamSource...)
*/
public enum ParamSource {

/**
* Source equivalent to {@link Context#path(String)}.
*/
PATH(Context::path),

/**
* Source equivalent to {@link Context#header(String)}.
*/
HEADER(Context::header),

/**
* Source equivalent to {@link Context#cookie(String)}.
*/
COOKIE(Context::cookie),

/**
* Source equivalent to {@link Context#flash(String)}.
*/
FLASH(Context::flash),

/**
* Source equivalent to {@link Context#session(String)}.
*/
SESSION(Context::session),

/**
* Source equivalent to {@link Context#query(String)}.
*/
QUERY(Context::query),

/**
* Source equivalent to {@link Context#form(String)}.
*/
FORM(Context::form),

/**
* Source equivalent to {@link Context#multipart(String)}.
*/
MULTIPART(Context::multipart);

final BiFunction<Context, String, Value> provider;

ParamSource(BiFunction<Context, String, Value> provider) {
this.provider = provider;
}
}
Loading