Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom body patterns and rework content pattern deserialization. #2592

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

kyle-winkelman
Copy link
Contributor

Allow users to define ContentPatternExtensions to support custom body patterns. Extensions will register them against the ContentPatternDeserializer to allow the ContentPattern (or StringValuePattern) subclasses to be supplied via json mapping files.

In order to allow these new ContentPatterns to support an arbitrary number and variety of parameters the ContentPatternDeserialiser was modified to simply identify the appropriate class and delegate to the @JsonCreator of that class.

This is superior to using custom-matching:

  • Multiple bodyPatterns are able to be supplied whereas only a single customMatcher can be supplied.
  • The diff for bodyPatterns are explicitly listed and easier to understand than the generic message for customMatcher.
  • A StringValuePattern has more reusability with other components (e.g. matchesJsonPath, matchesXPath, and, or, not, etc.)

References

  • Will link wiremock.org PR in future

Submitter checklist

  • Recommended: Join WireMock Slack to get any help in #help-contributing or a project-specific channel like #wiremock-java
  • The PR request is well described and justified, including the body and the references
  • The PR title represents the desired changelog entry
  • The repository's code style is followed (see the contributing guide)
  • Test coverage that demonstrates that the change works as expected
  • For new features, there's necessary documentation in this pull request or in a subsequent PR to wiremock.org

@kyle-winkelman kyle-winkelman requested a review from a team as a code owner January 25, 2024 18:10
@kyle-winkelman kyle-winkelman force-pushed the customBodyMatcher branch 2 times, most recently from 6c82031 to 8adf840 Compare January 25, 2024 18:32
Copy link
Contributor Author

@kyle-winkelman kyle-winkelman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes represented in AdminApiTest are a change in behavior allowing any value that can be converted to String, not just TextNodes.

@tomakehurst tomakehurst self-assigned this Feb 6, 2024
@kyle-winkelman
Copy link
Contributor Author

@tomakehurst, I see you self-assigned this. I was curious if you had any thoughts on this PR. My main motivation is for improving wiremock-graphql-extension to expose a reusable EqualToGraphqlPattern.

@tomakehurst
Copy link
Member

It's a change I'd definitely like to make, but reviewing a 31 file PR requires a level of uninterrupted focus I'm not not getting much of at the moment.

Also the priority is sorting out this Jetty 12 compatibility issue as nearly everyone using Spring is complaining about it. After that's shipped I should be able to take a proper look at this.

}

public static void registerPatterns(Map<String, Class<? extends ContentPattern<?>>> patterns) {
patterns.forEach(PATTERNS::putIfAbsent);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with this approach is when there are multiple WM instances running in the same JVM. Most of the time it'll probably be fine, but produce weird edge cases when not.

So far we've kept global static state out of WireMock for precisely this reason and I'd be reluctant to start introducing it here.

From my analysis a while back, there are 2 other approaches that could be made to work per-instance:

  1. Make Json a service that's injected everywhere it's needed, then custom serializers can be attached to the ObjectMapper it wraps. This would be the cleanest way to do it but is a pretty large amount of work.
  2. Deserialize unknown matcher keys into a Map (e.g. via Jackson's @JsonAnySetter / @JsonAnyGetter) then later associate them with matcher extensions that have been registered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just pushed some code for another option:
3. Use thread local state instead of global static state. Before performing any operations that deserializes a StubMapping or StubMappingCollection, use the Extensions of the currently running WM instance to supply ContentPatternDeserialiser with the extensions that have been registered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Seems too complex of a change.
  2. I may spend some time looking at this option, but I think it will look a lot like my current code. Where I make changes in the locations that deserialize StubMapping and StubMappingCollection, but instead of tampering with ContentPatternDeserialiser, I would post process the object to create concrete stub mappings from the generic map.

@tomakehurst
Copy link
Member

Hey, sorry for taking so long to review this.

I think this is an improvement over having to live with shared/static state, I still have a "there be dragons" feeling about using thread locals on this way, based on repeated previous experiences of edge cases, hard-to-debug problems and difficulty setting up tests correctly.

I do have an idea derived from yours that could get us to a better place however: modify the Json class so that it can be an instance injected as a service, but also set the threadlocal static instance to the one configured with the WireMock instance's settings. That way we can avoid having to do a big-bang cutover to the injected service approach, and migrate towards it over time, strangling the static use of Json over time.

@kyle-winkelman
Copy link
Contributor Author

kyle-winkelman commented May 21, 2024

I have one more simple idea I would love for you to review when you have the time (just look at second commit as first is largely unchanged). This approach will attempt to use the built-in patterns first, but will then fall back to looking for a class field and loading that custom pattern if no built-in is matched.

Pros:

  • Easy to extend and understand
  • Smaller code change

Cons:

  • Doesn't follow existing extension methodology (if it is on the classpath it will work)

Example:

{
    "class": "a.b.c.CustomStringValuePattern",
    "customStringValue": "a",
    "additionalProperty": "b"
}
  @JsonDeserialize
  public class CustomStringValuePattern extends StringValuePattern {

    @JsonCreator
    public CustomStringValuePattern(@JsonProperty("customStringValue") String customStringValue, @JsonProperty("additionalProperty") String additonalProperty) {
      super(customStringValue);
    }

    @Override
    public MatchResult match(String value) {
      return MatchResult.of(false); // TODO
    }
  }

@tomakehurst
Copy link
Member

I'm not really keen on this idea I'm afraid, as it means a) (as you say) there'd be two ways to add extensions, b) it wouldn't be possible to inject WireMock services or otherwise define your own construction logic.

@kyle-winkelman kyle-winkelman force-pushed the customBodyMatcher branch 2 times, most recently from 416a93e to 2f2d96d Compare May 22, 2024 19:29
@kyle-winkelman kyle-winkelman force-pushed the customBodyMatcher branch 2 times, most recently from 42f0c7d to 6bf5d64 Compare May 23, 2024 15:52
@kyle-winkelman
Copy link
Contributor Author

Thanks @tomakehurst for your patience with this PR.

Currently running into issue around HttpAdminClient and RemoteMappingsLoader as there is currently no way for the HttpAdminClient to ask the remote server what Extensions it has.

  • Should I add an Admin endpoint that allows to query for that?
  • What should behavior be if local JVM doesn't have those Extensions on its classpath (for remote wiremock server)? For example, HttpAdminClient#listAllStubMappings() may need to parse some custom body matchers that it doesn't have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants