When writing a Spring Cloud Contract in Groovy,
I want to specify an explicit JSON path expression.
The expression:
"$.['variants'][*][?(#.['name'] == 'product_0004' && #.['selected'] == true)]"
shall appear in the generated json, like so:
{
"request" : {
"bodyPatterns": [ {
"matchesJsonPath": "$.['variants'][*][?(#.['name'] == 'product_0004' && #.['selected'] == true)]"
} ]
}
}
in order to match e.g.:
{ "variants": [
{ "name": "product_0003", "selected": false },
{ "name": "product_0004", "selected": true },
{ "name": "product_0005", "selected": false } ]
}
and to not match e.g.:
{ "variants": [
{ "name": "product_0003", "selected": false },
{ "name": "product_0004", "selected": false },
{ "name": "product_0005", "selected": true } ]
}
Is this possible using consumers, bodyMatchers, or some other facility of the Groovy DSL?
There are some possibilities with matching on json path, but you wouldn't necessarily use it for matching on explicit values, but rather to make a flexible stub for the consumer by using regex. There are some possibilities though.
So the body section is your static request body with hardcoded values, while the bodyMatchers section provides you the ability to make the stub matching from the consumer side more flexible.
Contract.make {
request {
method 'POST'
url '/some-url'
body ([
id: id
items: [
foo: foo
bar: bar
],
[
foo: foo
bar: foo
]
])
bodyMatchers {
jsonPath('$.id', byEquality()) //1
jsonPath('$.items[*].foo', byRegex('(?:^|\\W)foo(?:$|\\W)')) //2
jsonPath('$.items[*].bar', byRegex(nonBlank())) //3
}
headers {
contentType(applicationJson())
}
}
response {
status 200
}
}
I referenced some lines
1: "byEquality()" in the bodyMatchers section means: the input from the consumer must be equal to the value provided in the body for this contract/stub to match, in other words must be "id".
2: I'm not sure how nicely the //1 solution will work when the property is in a list, and you want the stub to be flexible with the amount of items provided. Therefor I also included this byRegex which basically means, for any item in the list, the property foo must have exactly value "foo". However, I dont really know why you would want to do this.
3: This is where bodyMatchers are actually most useful. This line means: match to this contract if every property bar in the list of items is a non blank string. This allows you to have a dynamic stub with a flexible size of lists/arrays.
All the conditions in bodyMatchers need to be met for the stub to match.
Related
I am in no way an expert with groovy so please don't hold that against me.
I have JSON that looks like this:
{
"metrics": [
{
"name": "metric_a",
"help": "This tracks your A stuff.",
"type": "GAUGE",
"labels": [
"pool"
],
"unit": "",
"aggregates": [],
"meta": [
{
"category": "CAT A",
"deployment": "environment-a"
}
],
"additional_notes": "Some stuff (potentially)"
},
...
]
...
}
I'm using it as a source for automated documentation of all the metrics. So, I'm iterating through it in various ways to get the information I need. So far so good, I'm most of the way there. The problem is this all needs to be organized per the deployment environment. Meaning, multiple metrics will share the same value for deployment.
My thought was I could create a map with deployment as the key and the metric name for any metric that has a matching deployment as the value. Once I have that map, it should be easy for me to organize things the way they should be. I can't figure out how to do that. The result is all the metric names are added which is expected since I'm not doing anything to filter them out. I was thinking that groupBy would make sense here but I can't figure out how to use it effectively and frankly I'm not sure it will solve my problem by itself. Here is my code so far:
parentChild = [:]
children = []
metrics.each { metric ->
def metricName = metric.name
def depName = metric.meta.findResult{ it.deployment }
children.add(metricName)
parentChild.put(depName, children)
}
What is the best way to create a new map where the values for each key are based off a specific condition?
EDIT: The desired result would be each key in the resulting map would be a unique deployment value from all the metrics (as a string). Each value would be name of each metric that contains that deployment (as an array).
[environment-a:
[metric_a,metric_b,metric_c,...],
environment-b:
[metric_d,metric_e,metric_f,...]
...]
I would use a combo of withDefault() to pre-fill each map-entry value with a fresh TreeSet-instance (sorted no-duplicates set) and standard inject().
I reduced your sample data to the bare minimum and added some new nodes:
import groovy.json.*
String input = '''\
{
"metrics": [
{
"name": "metric_a",
"meta": [
{
"deployment": "environment-a"
}
]
},
{
"name": "metric_b",
"meta": [
{
"deployment": "environment-a"
}
]
},
{
"name": "metric_c",
"meta": [
{
"deployment": "environment-a"
},
{
"deployment": "environment-b"
}
]
},
{
"name": "metric_d",
"meta": [
{
"deployment": "environment-b"
}
]
}
]
}'''
def json = new JsonSlurper().parseText input
def groupedByDeployment = json.metrics.inject( [:].withDefault{ new TreeSet() } ){ res, metric ->
metric.meta.each{ res[ it.deployment ] << metric.name }
res
}
assert groupedByDeployment.toString() == '[environment-a:[metric_a, metric_b, metric_c], environment-b:[metric_c, metric_d]]'
If your metrics.meta array is supposed to have a single value, you can simplify the code by replacing the line:
metric.meta.each{ res[ it.deployment ] << metric.name }
with
res[ metric.meta.first().deployment ] << metric.name
I have the following documents:
{
"_id": "doc1"
"binds": {
"subject": {
"Test1": ["something"]
},
"object": {
"Test2": ["something"]
}
},
},
{
"_id": "doc2"
"binds": {
"subject": {
"Test1": ["something"]
},
"object": {
"Test3": ["something"]
}
},
}
I need a Mango selector that retrieves documents where any field inside binds (subject, object etc) has an object with key equals to any values from an array passed as parameter. That is, if keys of binds contains any values of some array it should returns that document.
For instance, consider the array ["Test2"] my selector should retrieve doc1 since binds["subject"]["Test1"] exists; the array ["Test1"] should retrieve doc1 and doc2 and the array ["Test2", "Test3"] should also retrieve doc1 and doc2.
F.Y.I. I am using Node.js with nano lib to access CouchDB API.
I am providing this answer because the luxury of altering document "schema" is not always an option.
With the given document structure this cannot be done with Mango in any reasonable manner. Yes, it can be done, but only when employing very brittle and inefficient practices.
Mango does not provide an efficient means of querying documents for dynamic properties; it does support searching within property values e.g. arrays1.
Using worst practices, this selector will find docs with binds properties subject and object having properties named Test2 and Test3
{
"selector": {
"$or": [
{
"binds.subject.Test2": {
"$exists": true
}
},
{
"binds.object.Test2": {
"$exists": true
}
},
{
"binds.subject.Test3": {
"$exists": true
}
},
{
"binds.object.Test3": {
"$exists": true
}
}
]
}
}
Yuk.
The problems
The queried property names vary so a Mango index cannot be leveraged (Test37 anyone?)
Because of (1) a full index scan (_all_docs) occurs every query
Requires programmatic generation of the $or clause
Requires a knowledge of the set of property names to query (Test37 anyone?)
The given document structure is a show stopper for a Mango index and query.
This is where map/reduce shines
Consider a view with the map function
function (doc) {
for(var prop in doc.binds) {
if(doc.binds.hasOwnProperty(prop)) {
// prop = subject, object, foo, bar, etc
var obj = doc.binds[prop];
for(var objProp in obj) {
if(obj.hasOwnProperty(objProp)) {
// objProp = Test1, Test2, Test37, Fubar, etc
emit(objProp,prop)
}
}
}
}
}
So the map function creates a view for any docs with a binds property with two nested properties, e.g. binds.subject.Test1, binds.foo.bar.
Given the two documents in the question, this would be the basic view index
id
key
value
doc1
Test1
subject
doc2
Test1
subject
doc1
Test2
object
doc2
Test3
object
And since view queries provide the keys parameter, this query would provide your specific solution using JSON
{
include_docs: true,
reduce: false,
keys: ["Test2","Test3"]
}
Querying that index with cUrl
$ curl -G http://{view endpoint} -d 'include_docs=false' -d
'reduce=false' -d 'keys=["Test2","Test3"]'
would return
{
"total_rows": 4,
"offset": 2,
"rows": [
{
"id": "doc1",
"key": "Test2",
"value": "object"
},
{
"id": "doc2",
"key": "Test3",
"value": "object"
}
]
}
Of course there are options to expand the form and function of such a view by leveraging collation and complex keys, and there's the handy reduce feature.
I've seen commentary that Mango is great for those new to CouchDB due to it's "ease" in creating indexes and the query options, and that map/reduce if for the more seasoned. I believe such comments are well intentioned but misguided; Mango is alluring but has its pitfalls1. Views do require considerable thought, but hey, that's we're supposed to be doing anyway.
1) $elemMatch for example require in memory scanning which can be very costly.
In logic app after executing SQL_Exceute_Query I got result like below -
{
"OutputParameters": {},
"ResultSets": {}
}
How can I check ResultSets do not have any data using expression?
I tried like body('Exceute_a_sql_query')?['ResultSets'] is not equal to '{}' in condition connector but not working for me.
If body('Exceute_a_sql_query')?['ResultSets'] having data go to if otherwise go to else
I tried both is equal to and is not equal to, they both work. However you couldn't define it with designer flow, you have to define it with Code view, or it won't accept {} as a value.
I define a variable with the ResultSets value.
Hope this could help you.
Another possible solution if you don't want to init a variable is to write it as:
"expression": {
"and": [
{
"not": {
"equals": [
"#{body('Exceute_a_sql_query')?['ResultSets']}",
"{}"
]
}
}
]
}
But as said before, you need to edit the expression in Code view. And make sure that {} are around the body statement.
"expression": {
"and": [
{
"equals": [
"#empty(body('Execute_a_SQL_query_(V2)')?['resultsets'])",
false
]
}
]
},
I have a For_Each loop in an Azure Logic App that calls another, nested, Logic App. The result from each iteration of the nested Logic Apps is a JSON object that contains an array of strings, like this:
{
"Results": ["string a", "string b"]
}
So the output from my For_Each loop in the parent Logic App looks like this:
[
{"Results": ["string a", "string b"]},
{"Results": ["string c", "string d"]}
]
I want to put all these strings into a single flat list that I can pass to another action.
How can I do this? Is it possible using the workflow definition language and built-in functions, or do I need to use an external function (in a service, or an Azure Function)?
There's a simpler solution, working with Array Variables.
At the top level, outside the For Each loop, declare a variable with an InitializeVariable action:
"Initialize_Items_variable": {
"inputs": {
"variables": [
{
"name": "Items",
"type": "Array",
"value": []
}
]
},
"runAfter": {},
"type": "InitializeVariable"
}
Inside the For Each, use a AppendToArrayVariable action. You can append the Response object of the Nested Logic App you just called.
"Append_to_Items_variable": {
"inputs": {
"name": "Items",
"value": "#body('Nested_Logic_App_Response')"
},
"runAfter": {
},
"type": "AppendToArrayVariable"
}
Hope it helps.
Picking up on #DerekLi's useful comment above, it seems this is not possible at the time of writing with Logic Apps schema version 2016-06-01.
One of the great strengths of Logic Apps is the ability to leverage the power of Azure Functions to solve problems like this that can't (yet) be solved in the schema language.
Re-writing the array is trivial in c# within a function:
using System.Net;
public class Result
{
public List<string> Results {get; set;}
}
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var inputs = await req.Content.ReadAsAsync<List<Result>>();
var outputs = new List<string>();
foreach(var item in inputs)
{
log.Info(item.Results.ToString());
outputs.AddRange(item.Results.Where(x => !string.IsNullOrEmpty(x)));
}
return req.CreateResponse(HttpStatusCode.OK, outputs);
}
And this function can then be passed the result of the For_Each loop:
"MyFunction": {
"inputs": {
"body": "#body('Parse_JSON')",
"function": {
"id": "/subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.Web/sites/{function-app-name}/functions/{function-name}"
},
"method": "POST"
},
"runAfter": {
"For_each": [
"Succeeded"
]
},
"type": "Function"
}
There is also a way to do it using the workflow definition language. (https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-workflow-definition-language).
Using the fonctions string and replace you can work on your json as a string rather than on objects.
Here is a Flat_List action that follows a Parse_JSON action with your data:
Your data:
[
{"Results": ["string a", "string b"]},
{"Results": ["string c", "string d"]}
]
Flat_List component:
"Flat_List": {
"inputs": "#replace(replace(replace(string(body('Parse_JSON')),']},{\"Results\":[',','),'}]','}'),'[{','{')",
"runAfter": {
"Parse_JSON": [
"Succeeded"
]
},
"type": "Compose"
},
What happens here? First we use string that takes your json data and gives:
[{"Results":["string a", "string b"]},{"Results":["string c", "string d"]}]
We replace all the ]},{"Results":[ by ,.
We replace all the }] by }.
We replace all the [{ by {.
We get the string {"Results":["string a","string b","string c","string d"]}
Then you are free to parse it back to json with:
"Parse_JSON_2": {
"inputs": {
"content": "#outputs('Flat_List')",
"schema": {
"properties": {
"Results": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
},
"runAfter": {
"Flat_List": [
"Succeeded"
]
},
"type": "ParseJson"
}
You can see it as a proof of concept as the Azure Function may be easier to re-read later but there may be many reason not to want to instantiate a new Azure Function while you can do the job in Logic App.
Feel free to ask for more details if needed :)
This technique works pretty well, and only uses run-of-the-mill Logic App actions:
1. start with declaring an empty array variable (action Variable: Initialise variable)
2. iterate through your items (action Control: For each), e.g. the resultset from a previous action
in each iteration, first compose the JSON fragment you need (action Data Operations: Compose)
then append the output of your Compose action to the array (action: Variable: Append to array variable)
3. then, outside the loop, join the elements of the array (action Data Operations: Join)
4. do what you need with the output of the Join action, e.g. send as response payload (action Request: Response)
This is what it looks like in the end:
You can use #body(nestedLogicApp) outside of the for-each loop to access all the nested Logic Apps' response in an array.
Let's say we have a nested data structure like so:
[
{
"name": "fruits",
"items": [
{ "name": "apple" ...}
{ "name": "lemon" ...}
{ "name": "peach" ...}
]
}
{
"name": "veggies",
"items": [
{ "name": "carrot" ...}
{ "name": "cabbage" ...}
]
}
{
"name": "meat",
"items": [
{ "name": "steak" ...}
{ "name": "pork" ...}
]
}
]
The above data is placed in a dojo/store/Memory. I want to perform a query for items that contain the letter "c", but only on the lower level (don't want to query the categories).
With a generic dojo/store/Memory, it's query function only applies a filter on the top level, so the code
store.query(function(item) {
return item.name.indexOf("c") != -1;
});
will only perform the query on the category names (fruits, veggies, etc) instead of the actual items.
Is there a straight-forward way to perform this query on the child nodes, and if there's a match, return all children as well as the parent? For instance, the "c" query would return the "fruits" node with it's "peach" child only, "veggies" would remain intact, and "meat" would be left out of the query results entirely.
You can of course define your own checking method in the store's query method. I don't check if this code runs perfectly, but I guess you could pretty much get what it's meant to do.
store.query(function(item) {
var found = {
name: "",
items: []
};
var children = item.items;
d_array.forEach(children, function(child) {
if (child.name.indexOf("c") != -1) {
found.name = item.name;
found.items.push(child);
}
});
return found;
});
Hope this helps.