dreamhead / moco

Easy Setup Stub Server
MIT License
4.34k stars 1.08k forks source link

Loading JSONs from external source #278

Open pun-ky opened 4 years ago

pun-ky commented 4 years ago

any suggestions on how to implement loading JSON files from JCR instead of the filesystem will be appreciated.

I am implementing a tool for Adobe Experience Manager which will be based on Moco which will run on OSGi. Yes it is possible (there is a little problem with Guava which you are using but nvm). On AEM, all assets are just stored on JCR, so that's the only way to go. In Wiremock, I have seen FileSource interface, which seems that supports abstraction for loading JSONs from any sources. I hope that Moco is having sth like this too.

dreamhead commented 4 years ago

You can try proxy API in current version, which can load any URL as response.

Tell me if it works for you.

pun-ky commented 4 years ago

you didn't get me ;)

I'd like to implement sth like that

server.request(by("foo")).response(jsonFileInJcr("/etc/stubs/foo-response.json");

any hints appreciated

pun-ky commented 4 years ago

I don't want to access that using URL / HTTP and any other protocol. My JCR storage is a same OSGi container / Moco is running at same server as JCR - I have programmatic access to read that data from JCR so using HTTP is not an option (overhead).

dreamhead commented 4 years ago

So, what you want is an interface to allow you extend to access your content, isn't it?

BTW, is JCR Content repository API for Java?

pun-ky commented 4 years ago

yep, yep :)

that repo looks like this:

image

I am implementing a tool that allows writing stub scripts that are configuring Moco HTTP server. I'd like to be able to easily respond content just read from the repository (not always a same string, but content from file stored in a repository that may change in the meantime)

pun-ky commented 4 years ago

hmm in other words... I am asking for .. maybe there is sth like response(Supplier<String>) ?

dreamhead commented 4 years ago

No such interface for now, but it's not hard to support.

I can provide the interfaces like the following:

text(Supplier<Object>);
text(Function<Request, Object>);
json(Supplier<Object>);
json(Function<Request, Object>);

so, you can write your API like this:

server.request(...).response(json(() -> new YourPojo());
server.request(...).response(json((request) -> new YourPojo());
pun-ky commented 4 years ago

It would be nice 🙂

What if I have no Pojo class and want to supply json string read from repo file?

pun-ky commented 4 years ago

how about a supplier for template variables? snippet in Groovy that I would like to support:

stubs.server
        .get(by(uri("/current-date")))
        .response(template('Today date is "${date}"', "date", dynamicVar({ new Date() })))
pun-ky commented 4 years ago

I implemented it as :

public final class DynamicExtractor<T> implements RequestExtractor<T> {

  private final Supplier<T> objectSupplier;

  public DynamicExtractor(final Supplier<T> objectSupplier) {
    this.objectSupplier = objectSupplier;
  }

  @Override
  public Optional<T> extract(final Request request) {
    return of(objectSupplier.get());
  }
}

public final class MocoExtensions {

  public static RequestExtractor<Object> dynamicVar(final Supplier<Object> textSupplier) {
    return new DynamicExtractor<>(checkNotNull(textSupplier, "Template variable supplier should not be null or empty"));
  }
}

and it works.

my doubt is only about Optional.of() vs Optional.ofNullable. In PlainExtractor there is of but I can imagine returning nulls sometimes and maybe ofNullable in both PlainExtractor and DynamicExtractor would be better.

btw why ImmutableMap is in template() method signature, maybe it would be better to make it immutable just after calling method acceptingMap<String, RequestExtractor<?>> to make it more universal / more nicely callable e.g from my Groovy scripts.. ;)

dreamhead commented 4 years ago

Cool, really good suggestion!

var for template is also a place which works for you. But template is just for specific scenario, so I can support both template and response.

pun-ky commented 4 years ago

Any ETA for release with improvements? 🙂

dreamhead commented 4 years ago

@pun-ky The json and var have been committed to the codebase.

You can run ./gradlew build to build your own JAR.

pun-ky commented 4 years ago

Cool. I will test it for sure and let you know if it's okay 🙂

pun-ky commented 4 years ago

https://github.com/dreamhead/moco/commit/40d2cb88b6531e41775599bc7a91d0a96deaadaf

@dreamhead, potentially, I would like to create mocks not only for JSON files but also for text, XML, INI... almost any type of file (there should be no limitation type-based) and load them from the repository / be also supplied by function.

How would you address that case? I see that you implemented a JSON support only as requested originally in the issue, but to be honest, I was expecting sth more general.

pun-ky commented 4 years ago

@dreamhead , in other words, I'd like to address sth like below:

    server.response(with(text(() -> jcrRepository.readFileAsString("/some/node/my-response.xml"))), header("Content-Type", "application/xml"));
    server.response(with(text(() -> jcrRepository.readFileAsStream("/some/node/logs/my.log")));
    server.response(with(binary(() -> jcrRepository.readFileAsStream("/some/node/reports/sample-report.pdf"))), header("Content-Type", "application/pdf"));

It would be nice if that function returning stream will output response without redundantly allocating whole PDF (potentially big) in memory... or same case for mocking bigger log file

also, a supplier should be able to return a string in some cases because some external API could not return streams but strings... and it will be just easier to glue Moco with external API in that way (mock could be implemented more quickly)

pun-ky commented 4 years ago

about added overloaded signatures... there is a problem when using them from Groovy

groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method com.cognifide.aem.stubs.moco.Moco#var.
Cannot resolve which method to invoke for [class Script1$_run_closure1] due to overlapping prototypes between:
    [interface java.util.function.Function]
    [interface java.util.function.Supplier]

instead of simply:

stubs.server
        .get(by(uri("/current-date")))
        .response(template('Today date is "${date}"', "date", var({ new Date() })))

I need to write:

stubs.server
        .get(by(uri("/current-date")))
        .response(template('Today date is "${date}"', "date", var(new java.util.function.Supplier<Object>() {
                @Override
                public Object get () {
                    return new Date()  
                }
            } 
        )))