Skip to content

Parameter lookup feature for Context #1968

Closed

Description

@imeszaros

In Jooby 1.x all types of parameters (query, path, etc..) could be acquired by calling the same method. This could led to some problems or confusion, which was nicely addressed by 2.x.

In some situations however it would be convenient to specify multiple places a parameter should be searched. I created a helper class for this, which can be called the following way:

final Value foo = ContextHelpers.lookup(ctx)
    .inPath()
    .inQuery()
    .get("foo");
Click to see the implementation.
public static ValueLookup lookup(Context context) {
	return new ValueLookupImpl(context);
}

private static class ValueLookupImpl implements ValueLookup.Stack {

	protected final Context context;
	protected List<Function<String, Value>> chain = new ArrayList<>();

	ValueLookupImpl(Context context) {
		this.context = context;
	}

	@Override
	public Stack inPath() {
		chain.add(context::path);
		return this;
	}

	@Override
	public Stack inHeader() {
		chain.add(context::header);
		return this;
	}

	@Override
	public Stack inCookie() {
		chain.add(context::cookie);
		return this;
	}

	@Override
	public Stack inFlash() {
		chain.add(context::flash);
		return this;
	}

	@Override
	public Stack inSession() {
		chain.add(context::session);
		return this;
	}

	@Override
	public Stack inQuery() {
		chain.add(context::query);
		return this;
	}

	@Override
	public Stack inForm() {
		chain.add(context::form);
		return this;
	}

	@Override
	public Stack inMultipart() {
		chain.add(context::multipart);
		return this;
	}

	@Override
	public Value get(String name) {
		if (chain.isEmpty()) {
			throw new IllegalStateException("Did you forget to call an in*() method?");
		}
		return chain.stream()
				.map(f -> f.apply(name))
				.filter(v -> !v.isMissing())
				.findFirst()
				.orElseGet(() -> Value.missing(name));
	}
}

public interface ValueLookup {

	Stack inPath();
	Stack inHeader();
	Stack inCookie();
	Stack inFlash();
	Stack inSession();
	Stack inQuery();
	Stack inForm();
	Stack inMultipart();

	interface Stack extends ValueLookup {

		Value get(String name);

		default Optional<ValueNode> getNode(String name) {
			return Optional.of(get(name))
					.map(v -> v instanceof ValueNode ? (ValueNode) v : null);
		}
	}
}

If you would like to see something like this in Jooby, I could make a pull request. It could be used as a method of Context:

final Value foo = ctx.lookup()
    .inPath()
    .inQuery()
    .get("foo");

Activity

  1. jknack commented on Sep 2, 2020

    @jknack
    Member

    What about ctx.lookup(String, LookupScope...) where LookupScope (not super sure about the name) is an Enum?

  2. imeszaros commented on Sep 2, 2020

    @imeszaros
    MemberAuthor

    The reason for this design was that it's very convenient to write, i mean it's a fluent interface, you immediately get assist form the IDE. I think using an enum would somewhat break this convenience.

    What are your points, would you like to avoid additional interfaces?

  3. jknack commented on Sep 2, 2020

    @jknack
    Member

    Good argument. What about MVC?

  4. imeszaros commented on Sep 2, 2020

    @imeszaros
    MemberAuthor

    Yes that's a good question.. I was thinking on an annotation where you can specify the sources like @Param({ PATH, QUERY, ... }) but I dropped the idea because you can simply have the context as a parameter and then you can start a lookup on it.

  5. jknack commented on Sep 2, 2020

    @jknack
    Member

    yea, that was the reason of suggesting an enum approach. To "mimic" the MVC usage.

    Let's add it to Context for now as you suggested.

  6. imeszaros commented on Sep 2, 2020

    @imeszaros
    MemberAuthor

    I see your point.. what if we do both?

    enum ParamSource {
        PATH, QUERY, FORM ...
    }
    
    public @interface Param {
    
        String name();
    
        ParamSource[] sources();
    }

    and in Context we could add both:

    Value lookup(String, ParamSource...)
    
    ValueLookup lookup()

    The later would simply call the former at the end..

  7. jknack commented on Sep 2, 2020

    @jknack
    Member

    Go for it!

  8. added this to the 2.9.0 milestone on Sep 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions