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

Feature: Support for JsonPath in the response (with HandleBars) #167

Closed
zubinix opened this issue Jul 18, 2018 · 19 comments
Closed

Feature: Support for JsonPath in the response (with HandleBars) #167

zubinix opened this issue Jul 18, 2018 · 19 comments
Assignees
Labels

Comments

@zubinix
Copy link

zubinix commented Jul 18, 2018

Does WireMock.Net have support for the jsonPath helper found in the java based WireMock? I want to create a template that inserts in the response items from a JSON request. It seems all I can do with WireMock-Net 'out of the box' is use {{request.body}} in the response template which returns the entire JSON body of the request?

@StefH
Copy link
Collaborator

StefH commented Jul 18, 2018

Currently it's only supported that you can use a jsonmatcher on the request. See https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing-and-Request-Matching#json-jsonmatcher

It's not yet supported that you can use JsonPath in the output, like you can do with handlebars : https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing-and-Request-Matching#response-templating

@StefH StefH changed the title Support for jsonPath helper? Feature: Support for JsonPath in the response (like HandleBars) Jul 18, 2018
@zubinix
Copy link
Author

zubinix commented Jul 18, 2018

Is there a plan to do so? Is there a HandleBar feature I can use instead to extract parts from the request body?

@StefH
Copy link
Collaborator

StefH commented Jul 18, 2018

There is no HandleBars functionality out of the box. I can extend the code to support something like this:

// select single token
"{{JSONPath.SelectToken response.body \"$.Manufacturers[?(@.Name == 'Acme Co')]\"}}"

Where response.body is the response body in json.
And the $.Manufacturers[?(@.Name == 'Acme Co')] is the JSONPath expression (copied from example here: https://www.newtonsoft.com/json/help/html/QueryJsonSelectTokenJsonPath.htm)

Also selecting multiple tokens should be possible:

// select multiple tokens
"{{#JSONPath.SelectTokens response.body \"$..Products[?(@.Price >= 50)].Name\"}}{{token}}{{/JSONPath.SelectTokens}}"

@StefH StefH changed the title Feature: Support for JsonPath in the response (like HandleBars) Feature: Support for JsonPath in the response (with HandleBars) Jul 18, 2018
@zubinix
Copy link
Author

zubinix commented Jul 19, 2018

That sounds great. When do you think you can add this feature?

@StefH
Copy link
Collaborator

StefH commented Jul 19, 2018

Currently I am fixing this issue #148 (comment).

After that I will continue on this one.

If the proposed fix here is enough for you and will comply to all your requirements, I will implement it as is.

@StefH
Copy link
Collaborator

StefH commented Jul 19, 2018

Preview NuGet can be found at
https://www.nuget.org/packages/WireMock.Net/1.0.4.7-preview-jsonpath

Note that the syntax has been changed from JSONPath.SelectToken to JsonPath.SelectToken !

@zubinix
Copy link
Author

zubinix commented Jul 20, 2018

Thanks StefH. I will take a look and get back to you.

@zubinix
Copy link
Author

zubinix commented Jul 20, 2018

My request input I'm trying to match is

{"username": "Zubin"}

using

    "Response": {
      "UseTransformer": true,
      "StatusCode": 200,
      "BodyAsJson": { "result": "{{JsonPath.SelectToken response.body \"@.username\"}}" },
      "Headers": {
        "Content-Type": "application/json"
      }
    }

but I get

{
    "Status": "{\"ClassName\":\"System.NotSupportedException\",\"Message\":\"The value '' with type HandlebarsDotNet.Compiler.UndefinedBindingResult cannot be used in Handlebars JsonPath.\",\"Data\":null,\"InnerException\":null,\"HelpURL\":null,\"StackTraceString\":\"   at WireMock.Transformers.HandlebarsHelpers.Parse(Object[] arguments)\\r\\n   at WireMock.Transformers.HandlebarsHelpers.<>c.<Register>b__0_0(TextWriter writer, Object context, Object[] arguments)\\r\\n   at lambda_method(Closure , TextWriter , Object )\\r\\n   at HandlebarsDotNet.Handlebars.HandlebarsEnvironment.<>c__DisplayClass7_0.<Compile>b__0(Object context)\\r\\n   at WireMock.Transformers.ResponseMessageTransformer.Transform(RequestMessage requestMessage, ResponseMessage original)\\r\\n   at WireMock.ResponseBuilders.Response.ProvideResponseAsync(RequestMessage requestMessage)\\r\\n   at WireMock.Mapping.ResponseToAsync(RequestMessage requestMessage)\\r\\n   at WireMock.Owin.WireMockMiddleware.Invoke(HttpContext ctx)\",\"RemoteStackTraceString\":null,\"RemoteStackIndex\":0,\"ExceptionMethod\":null,\"HResult\":-2146233067,\"Source\":\"WireMock.Net\",\"WatsonBuckets\":null}"
}

What am I doing wrong?

@StefH
Copy link
Collaborator

StefH commented Jul 20, 2018

1] You want to use the request I guess
2] Also try request.bodyAsJson instead of request.body?

So: {{JsonPath.SelectToken request.bodyAsJson \"@.username\"}}

See also this code : https://github.com/WireMock-Net/WireMock.Net/blob/SupportJSONPath_in_the_response/test/WireMock.Net.Tests/ResponseBuilderTests/ResponseWithHandlebarsTests.cs#L384

@StefH StefH self-assigned this Jul 20, 2018
@StefH StefH added the feature label Jul 20, 2018
@zubinix
Copy link
Author

zubinix commented Jul 20, 2018

Yep should have used request.body but still getting

{
    "Status": "{\"ClassName\":\"System.NotSupportedException\",\"Message\":\"The value '' with type HandlebarsDotNet.Compiler.UndefinedBindingResult cannot be used in Handlebars JsonPath.\",\"Data\":null,\"InnerException\":null,\"HelpURL\":null,\"StackTraceString\":\"   at WireMock.Transformers.HandlebarsHelpers.Parse(Object[] arguments)\\r\\n   at WireMock.Transformers.HandlebarsHelpers.<>c.<Register>b__0_0(TextWriter writer, Object context, Object[] arguments)\\r\\n   at lambda_method(Closure , TextWriter , Object )\\r\\n   at HandlebarsDotNet.Handlebars.HandlebarsEnvironment.<>c__DisplayClass7_0.<Compile>b__0(Object context)\\r\\n   at WireMock.Transformers.ResponseMessageTransformer.Transform(RequestMessage requestMessage, ResponseMessage original)\\r\\n   at WireMock.ResponseBuilders.Response.ProvideResponseAsync(RequestMessage requestMessage)\\r\\n   at WireMock.Mapping.ResponseToAsync(RequestMessage requestMessage)\\r\\n   at WireMock.Owin.WireMockMiddleware.Invoke(HttpContext ctx)\",\"RemoteStackTraceString\":null,\"RemoteStackIndex\":0,\"ExceptionMethod\":null,\"HResult\":-2146233067,\"Source\":\"WireMock.Net\",\"WatsonBuckets\":null}"
}

The input request I'm trying to grab data from is very simple just:

{"username": "Zubin"}

I'm just trying to make "Zubin" appear the response.

@StefH
Copy link
Collaborator

StefH commented Jul 20, 2018

You should use username instead of @.username.
And the BodyAsJson does not (yet) work, so you need to use this solution:

{
    "Guid": "407e031f-c96b-429d-8a58-01c6a456fedd",
    "Priority": 0,
    "Request": {
        "Path": {
            "Matchers": [
                {
                    "Name": "WildcardMatcher",
                    "Pattern": "/zubinix",
                    "IgnoreCase": false
                }
            ]
        },
        "Methods": [
            "post"
        ]
    },
    "Response": {
        "StatusCode": 200,
        "BodyDestination": "SameAsSource",
        "Body": "{ \"result\": \"{{JsonPath.SelectToken request.body \"username\"}}\" }",
        "UseTransformer": true,
        "Headers": {
            "Content-Type": "application/json"
        }
    }
}

Note that you also can use request.bodyAsJson.

@zubinix
Copy link
Author

zubinix commented Jul 20, 2018

That did the trick. Thanks.

@StefH
Copy link
Collaborator

StefH commented Jul 20, 2018

I did update the code, so now also BodyAsJson (in the response) is supported, see this mapping:

{
  "Request": {
    "Path": {
      "Matchers": [
        {
          "Name": "WildcardMatcher",
          "Pattern": "/zubinix2",
          "IgnoreCase": false
        }
      ]
    },
    "Methods": [
      "post"
    ],
    "Body": {}
  },
  "Response": {
    "StatusCode": 200,
    "BodyAsJson": {
      "path": "{{request.path}}",
      "result": "{{JsonPath.SelectToken request.bodyAsJson \"username\"}}"
    },
    "UseTransformer": true,
    "Headers": {
      "Content-Type": "application/json"
    }
  }
}

New Nuget (1.0.4.8-preview-01) is available.

Please test

@zubinix
Copy link
Author

zubinix commented Jul 23, 2018

Trying the above. What could be causing this error?

{
    "Status": "{\"ClassName\":\"System.ArgumentNullException\",\"Message\":\"Value cannot be null.\",\"Data\":null,\"InnerException\":null,\"HelpURL\":null,\"StackTraceString\":\"   at WireMock.Validation.Check.NotNull[T](T value, String parameterName)\\r\\n   at WireMock.Transformers.HandlebarsHelpers.Parse(Object[] arguments)\\r\\n   at WireMock.Transformers.HandlebarsHelpers.<>c.<Register>b__0_0(TextWriter writer, Object context, Object[] arguments)\\r\\n   at HandlebarsDotNet.Handlebars.HandlebarsEnvironment.<>c__DisplayClass7_0.<Compile>b__0(Object context)\\r\\n   at WireMock.Transformers.ResponseMessageTransformer.WalkNode(JToken node, Object template)\\r\\n   at WireMock.Transformers.ResponseMessageTransformer.WalkNode(JToken node, Object template)\\r\\n   at WireMock.Transformers.ResponseMessageTransformer.TransformBodyAsJson(Object template, ResponseMessage original, ResponseMessage responseMessage)\\r\\n   at WireMock.Transformers.ResponseMessageTransformer.Transform(RequestMessage requestMessage, ResponseMessage original)\\r\\n   at WireMock.ResponseBuilders.Response.ProvideResponseAsync(RequestMessage requestMessage)\\r\\n   at WireMock.Mapping.ResponseToAsync(RequestMessage requestMessage)\\r\\n   at WireMock.Owin.WireMockMiddleware.Invoke(HttpContext ctx)\",\"RemoteStackTraceString\":null,\"RemoteStackIndex\":0,\"ExceptionMethod\":null,\"HResult\":-2147467261,\"Source\":\"WireMock.Net\",\"WatsonBuckets\":null,\"ParamName\":\"arguments[0]\"}"
}

For input

    "Response": {
      "UseTransformer": true,
      "StatusCode": 200,
      "BodyAsJson": {
      	"path": "{{request.path}}",
        "result": "{{JsonPath.SelectToken request.bodyAsJson \"username\"}}" 
      },
      "Headers": {
        "Content-Type": "application/json"
      }
    }

@StefH
Copy link
Collaborator

StefH commented Jul 23, 2018

When you send a request, do you also provide the Content-Type header with value application/json?

Like:
header

@zubinix
Copy link
Author

zubinix commented Jul 23, 2018

Oh yes, I was missing the header in the request. Works now but interesting if I delete the header it still works! So the header is required the first time only? I'm using fiddler and postman as follows:

  1. Add mapping (using postman).
  2. Send request without application/json header using postman -> error above received. Verified in fiddler.
  3. Edit request in fiddler and add application/json header -> correct response received.
  4. Send the original request without the header from postman -> correct response received without using header??? caching???

@StefH
Copy link
Collaborator

StefH commented Jul 23, 2018

Very strange. There is no caching on the request, that would break the whole WireMock functionality. For now, do you think it works as excepted ? If so, this issue can be closed, and then I'll create a new official NuGet.

@zubinix
Copy link
Author

zubinix commented Jul 23, 2018

Yes. Please close and create the NuGet. Thanks! I might create another issue relating to this apparent caching after get some more experience this tool.

@zubinix
Copy link
Author

zubinix commented Jul 23, 2018

Support for Json request parameters in response has been added.

@zubinix zubinix closed this as completed Jul 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants