I have created a very simple Precompiled function (copied code from tool generated):
public class Foo
{
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req)
{
//log.Info($"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
// Set name to query string or body data
name = name ?? data?.name;
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
}
The dll this resides in is copied to the Function's folder and is linked up in function.json like this:
{
"scriptFile": "ExternalFunction.dll",
"entryPoint": "ExternalFunction.Foo.Run",
"disabled": false,
"bindings": [
{
"authLevel": "function",
"name": "req",
"type": "httpTrigger",
"direction": "in"
},
{
"name": "res",
"type": "http",
"direction": "out"
}
]
}
This all works fine.
What I then wanted to do was add a private method to be called from the Run method, so (baby steps) I added this to the Foo class:
private static string Test()
{
return "Hello";
}
This results in these errors in the CLI tools:
error AF007: A method matching the entry point name provided in
configuration ('ExternalFunction.Foo.Run') does not exist. Your
function must contain a single public method, a public method named
'Run', or a public method matching the name specified in the
'entryPoint' metadata property. Function compilation error error
AF007: A method matching the entry point name provided in
configuration ('ExternalFunction.Foo.Run') does not exist. Your
function must contain a single public method, a public method named
'Run', or a public method matching the name specified in the
'entryPoint' metadata property.
Which is a very odd message as surely adding the private static method should have no effect on Functions being able to find the public method specified in function.json?!
Any ideas?
This is indeed odd.
I'll work on a repro and open an issue to address the problem if this turns out to be a defect (I'll update the issue or the results of my investigation), but in the meantime, you should be able to create those methods in a different class (static or otherwise) and call that method on that class.
Related
I'm running chaincode-java from fabric-samples.
#Transaction(intent = Transaction.TYPE.EVALUATE)
public ArrayList<Asset> GetAllAssets(final Context ctx) {
ChaincodeStub stub = ctx.getStub();
ArrayList<Asset> queryResults = new ArrayList<Asset>();
// To retrieve all assets from the ledger use getStateByRange with empty startKey & endKey.
// Giving empty startKey & endKey is interpreted as all the keys from beginning to end.
// As another example, if you use startKey = 'asset0', endKey = 'asset9' ,
// then getStateByRange will retrieve asset with keys between asset0 (inclusive) and asset9 (exclusive) in lexical order.
QueryResultsIterator<KeyValue> results = stub.getStateByRange("", "");
for (KeyValue result: results) {
Asset asset = genson.deserialize(result.getStringValue(), Asset.class);
System.out.println(asset);
queryResults.add(asset);
}
// final String response = genson.serialize(queryResults);
return queryResults;
}
The GetAllAssets() method was returning String, but I changed it to ArrayList.
As a result, GetAllAssets throws error when invoked.
$ peer chaincode query -C mychannel -n basic -c '{"Args":["GetAllAssets"]}'
Error: endorsement failure during query. response: status:500 message:"Unexpected error"
The log says
Thread[fabric-txinvoke:2,5,main] 11:15:01:224 INFO org.hyperledger.fabric.contract.ContractRouter processRequest Got invoke routing request
Thread[fabric-txinvoke:2,5,main] 11:15:01:226 INFO org.hyperledger.fabric.contract.ContractRouter processRequest Got the invoke request for:GetAllAssets []
Thread[fabric-txinvoke:2,5,main] 11:15:01:234 INFO org.hyperledger.fabric.contract.ContractRouter processRequest Got routing:GetAllAssets:org.hyperledger.fabric.samples.assettransfer.AssetTransfer
Thread[fabric-txinvoke:2,5,main] 11:15:01:274 SEVERE org.hyperledger.fabric.Logger error nulljava.lang.NullPointerException
at org.hyperledger.fabric.contract.execution.JSONTransactionSerializer.toBuffer(JSONTransactionSerializer.java:84)
at org.hyperledger.fabric.contract.execution.impl.ContractExecutionService.convertReturn(ContractExecutionService.java:89)
at org.hyperledger.fabric.contract.execution.impl.ContractExecutionService.executeRequest(ContractExecutionService.java:67)
at org.hyperledger.fabric.contract.ContractRouter.processRequest(ContractRouter.java:123)
at org.hyperledger.fabric.contract.ContractRouter.invoke(ContractRouter.java:134)
at org.hyperledger.fabric.shim.impl.ChaincodeInvocationTask.call(ChaincodeInvocationTask.java:106)
at org.hyperledger.fabric.shim.impl.InvocationTaskManager.lambda$newTask$17(InvocationTaskManager.java:265)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Thread[fabric-txinvoke:2,5,main] 11:15:01:276 SEVERE org.hyperledger.fabric.shim.impl.ChaincodeInvocationTask call [13733a23] Invoke failed with error code 500. Sending ERROR
Can I return List from a transaction? Besides String, what other types can I return? Is there any documentation that I can take a look?
Bit of background first; the ContractAPI that is available in Java, Go and Typescript is used to generate a 'model' of the overall contract including the data type that be passed and returned from transaction functions. (JavaScript supports a limited subset to the extent possible based on it's typing).
In order to support this there has to be a 'serializer' of some sort to process the data. The underlying chaincode API of just 'invoke(byte[]): byte[]' gives the developer the power to serialize how they wish though not all of us need to use that power.
There is a default 'serializer' in the ContractAPI; this can be swapped out if needed.
To specifically answer the question;
The return types can be:
strings,
numbers (for Java this is any of the primitive 'number' types)
booleans,
other types that have been annotated.
arrays of the above
For the 'other types', there are annotations that can be used to define types that can also be passed to and from the transaction functions.
You might see something like this:
#DataType()
public final class Complex {
#Property()
private final String id;
#Property()
private final Description description;
#Property()
private final int value;
public String getID() {
return id;
}
public int getValue() {
return value;
}
public Description getDescription(){
return description;
}
}
Description there is also a class annotated in a similar manner.
This would produce the Contract Metadata that would look like
"Description": {
"$id": "Description",
"type": "object",
"properties": {
"colour": {
"type": "string"
},
"owners": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"Complex": {
"$id": "Complex",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value": {
"type": "number"
},
"description": {
"$ref": "Description"
}
}
}
On the Contract Model, or Contract Metadata
There is a JSON schema for this at
https://github.com/hyperledger/fabric-chaincode-node/blob/main/apis/fabric-contract-api/schema/contract-schema.json
Isn't this restrictive? what about Lists?
It's a fair comment, from a Java perspective, something like an ArrayList or Map would be a reasonable thing to return. However the challenge is that it is possible for the contracts to be implemented in different languages. Plus once deployed the Contract will be running for some time, therefore the metadata provides a strong 'API Definition' between the Smart Contract and the Client Application.
What transaction functions (also in the metadata) will be clearly defined.
Summary
I would like to provide some more examples (and docs!) but wanted to get this written up first. There are extensions and changes we could make, and would like to make but there are only so many hours!
As a maintainer of these repos, we'd love to have people come on board if this is an area of interest.
I have a test that I cannot get the syntax correctly:
#Test
void statsTest() {
given().queryParam("param", "ball")
.when().get()
.then().body("total", is(closeTo(10.0, 0.1*10.0))));
}
However, the test keeps failing even though the condition is met:
java.lang.AssertionError: 1 expectation failed.
JSON path total doesn't match.
Expected: is a numeric value within <1.0> of <10.0>
Actual: 10
I've never had a problem with types before in this setup of RestAssured and Hamcrest. For example, a test of the sort: body("total", greaterThan(9)) works fine, which means that there is some type casting under the hood.
I've looked through the docs and cannot find a way to cast the value of body("total") to a numerical value.
so I suspect that this is a bug or I'm not understanding something here.
Here's the JSON response. I had to clip it to make is short. Hope this works.
{
"stats": {
"totalHits": 1,
"searchEngineTimeInMillis": 83,
"searchEngineRoundTripTimeInMillis": 87,
"searchProcessingTimeInMillis": 101
},
"products": {
"id": "total",
"displayName": "Documents",
"ball": 10}
}
The key value pair corresponding to key: "total" in your response seems to be of integer type. So it needs to be checked for bounds with integer based bounds (1,10). So instead of using the closeTo matcher, you can use the following matcher.
allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(10)))
I've put together another approach that solves the problem but with a slightly different approach. Much thanks to those who populate the web with their code samples. The following assumes you already have set the base URI and PATH. You can add a path deeper in the response by using the get("/path..."). This answer assumes a JSON type response.
private static Response getResponse(String paramName, String paramValue) {
return given().queryParam(paramName, paramValue)
.when().get();
}
public static String getJsonValue(String jsonPath, String paramName, String paramValue) {
Response response = getResponse(paramName, paramValue);
//response.getBody().prettyPrint();
JsonPath jsonPathEvaluator = response.jsonPath();
return jsonPathEvaluator.get(jsonPath).toString();
}
You can simply print the return value and cast it to the type you need.
The test then looks like this:
public static void checkIfNumberCloseToValue(String jsonPath,
String paramName,
String paramValue,
Double error,
Double expected) {
Double value = Double.valueOf(Utils.getJsonValue(jsonPath, paramName, paramValue));
double range = expected * error;
assertThat(value, closeTo(expected, range));
}
I need to bind to an output blob, but the blob path needs to be computed dynamically in my function. How do I do it?
Binder is an advanced binding technique that allows you to perform bindings imperatively in your code as opposed to declaratively via the function.json metadata file. You might need to do this in cases where the computation of binding path or other inputs needs to happen at runtime in your function. Note that when using an Binder parameter, you should not include a corresponding entry in function.json for that parameter.
In the below example, we're dynamically binding to a blob output. As you can see, because you're declaring the binding in code, your path info can be computed in any way you wish. Note that you can bind to any of the other raw binding attributes as well (e.g. QueueAttribute/EventHubAttribute/ServiceBusAttribute/etc.) You can also do so iteratively to bind multiple times.
Note that the type parameter passed to BindAsync (in this case TextWriter) must be a type that the target binding supports.
using System;
using System.Net;
using Microsoft.Azure.WebJobs;
public static async Task<HttpResponseMessage> Run(
HttpRequestMessage req, Binder binder, TraceWriter log)
{
log.Verbose($"C# HTTP function processed RequestUri={req.RequestUri}");
// determine the path at runtime in any way you choose
string path = "samples-output/path";
using (var writer = await binder.BindAsync<TextWriter>(new BlobAttribute(path)))
{
writer.Write("Hello World!!");
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
And here is the corresponding metadata:
{
"bindings": [
{
"name": "req",
"type": "httpTrigger",
"direction": "in"
},
{
"name": "res",
"type": "http",
"direction": "out"
}
]
}
There are bind overloads that take an array of attributes. In cases where you need to control the target storage account, you pass in a collection of attributes, starting with the binding type attribute (e.g. BlobAttribute) and inlcuding a StorageAccountAttribute instance pointing to the account to use. For example:
var attributes = new Attribute[]
{
new BlobAttribute(path),
new StorageAccountAttribute("MyStorageAccount")
};
using (var writer = await binder.BindAsync<TextWriter>(attributes))
{
writer.Write("Hello World!");
}
Have consolidated all of the information from this an other posts along with comments and created a blog post that demonstrates how to use Binder with a real world scenario. Thanks to #mathewc this became possible.
I'm using an Azure function to pick up messages from an event hub and send out notifications via the Azure notification hub. Works great! Now I wanted to see whether I could add tags to those messages in order to allow user targeting via those tags.
The output for the notification hub has a "tag expression" parameter which you can configure. But this seems to be a static text. I need to dynamically set these tags based on the message received from the event hub instead. I'm not sure whether you can somehow put dynamic content in there?
I also found that the constructor of the GcmNotification object that I'm using has an overload which allows a tag string to be used. But when I try that I get a warning on compile time stating this is deprecated and when the function fires it shows an error because the Tag property should be empty.
So I'm not clear on a) whether this is at all possible and b) how to do it when it is. Any ideas?
Update: as suggested I tried creating a POCO object to map to my input string. The string is as follows:
[{"deviceid":"repsaj-neptune-win10pi","readingtype":"temperature1","reading":22.031614503139451,"threshold":23.0,"time":"2016-06-22T09:38:54.1900000Z"}]
The POCO object:
public class RuleMessage
{
public string deviceid;
public string readingtype;
public object reading;
public double threshold;
public DateTime time;
}
For the function I now tried both RuleMessage[] and List<RuleMessage> as parameter types, but the function complains it cannot convert the input:
2016-06-24T18:25:16.830 Exception while executing function: Functions.submerged-function-ruleout. Microsoft.Azure.WebJobs.Host: Exception binding parameter 'inputMessage'. Microsoft.Azure.WebJobs.Host: Binding parameters to complex objects (such as 'RuleMessage') uses Json.NET serialization.
1. Bind the parameter type as 'string' instead of 'RuleMessage' to get the raw values and avoid JSON deserialization, or
2. Change the queue payload to be valid json. The JSON parser failed: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Submission#0+RuleMessage' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Function code:
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.Azure.NotificationHubs;
public static void Run(List<RuleMessage> inputEventMessage, string inputBlob, out Notification notification, out string outputBlob, TraceWriter log)
{
if (inputEventMessage == null || inputEventMessage.Count != 1)
{
log.Info($"The inputEventMessage array was null or didn't contain exactly one item.");
notification = null;
outputBlob = inputBlob;
return;
}
log.Info($"C# Event Hub trigger function processed a message: {inputEventMessage[0]}");
if (String.IsNullOrEmpty(inputBlob))
inputBlob = DateTime.MinValue.ToString();
DateTime lastEvent = DateTime.Parse(inputBlob);
TimeSpan duration = DateTime.Now - lastEvent;
if (duration.TotalMinutes >= 0) {
notification = GetGcmMessage(inputMessage.First());
log.Info($"Sending notification message: {notification.Body}");
outputBlob = DateTime.Now.ToString();
}
else {
log.Info($"Not sending notification message because of timer ({(int)duration.TotalMinutes} minutes ago).");
notification = null;
outputBlob = inputBlob;
}
}
private static Notification GetGcmMessage(RuleMessage input)
{
string message;
if (input.readingtype == "leakage")
message = String.Format("[FUNCTION GCM] Leakage detected! Sensor {0} has detected a possible leak.", input.reading);
else
message = String.Format("[FUNCTION GCM] Sensor {0} is reading {1:0.0}, threshold is {2:0.0}.", input.readingtype, input.reading, input.threshold);
message = "{\"data\":{\"message\":\""+message+"\"}}";
return new GcmNotification(message);
}
public class RuleMessage
{
public string deviceid;
public string readingtype;
public object reading;
public double threshold;
public DateTime time;
}
Update 28-6-2016: I've not managed to get it working by switching ASA output to line seperated to that it doesn't generate a JSON array any more. This is a temp. fix because the Function binding now fails as soon as there is more than one line in the output (which can happen).
Anyways, I now proceeded to set the tagExpression, as per instruction I changed it to:
{
"type": "notificationHub",
"name": "notification",
"hubName": "repsaj-neptune-notifications",
"connection": "repsaj-neptune-notifications_NOTIFICATIONHUB",
"direction": "out",
"tagExpression": "deviceId:{deviceid}"
}
Where {deviceid} equals the deviceid property on my RuleMessage POCO. Unfortunately this doesn't work, when I call the function it outputs:
Exception while executing function: Functions.submerged-function-ruleout. Microsoft.Azure.WebJobs.Host: Exception binding parameter 'notification'. Microsoft.Azure.WebJobs.Host: No value for named parameter 'deviceid'.
Which is not true, I know for sure the property has been set as I've logged it to the ouput window. I also tried something like {inputEventMessage.deviceid}, but that doesn't work either (as I didn't get how the runtime would map {deviceid} to the correct input object when there's more than one.
The tagExpression binding property supports binding parameters coming from trigger input properties. For example, assume your incoming Event Hub event has properties A and B. You can use these properties in your tagExpression using the parens syntax, e.g.: My Tag {A}-{B}.
In general, most of the properties across all the binding types support binding parameters in this way.
I wanted to create the summaries in code behind because all of my routes are currently configured in the AppConfig class, but as far as I can tell, summaries can only be included using the Route attribute.
EX:
[Route("/myrequest/{Id}, "GET", Summary="My Summary", Notes="My Notes")]
public class MyRequest : IReturn<MyResponse>
{
public int Id { get; set; }
}
yet my routes are configured like:
base.Routes
.Add<MyRequest>("/myrequest", "GET");
Essentially I'd like to do something like:
base.Routes
.Add<MyRequest>("/myrequest", "GET", "My Summary", "My Notes");
Is there currently a way to do this?
EDIT:
I'm using ServiceStack version 3.9.71
So I took another look at adding routes and found there actually is an overload that allows you to specify the summary and notes.
Here's how to do it:
base.Routes
.Add(typeof(MyRequest), "/myrequest", "GET", "My Summary", "My Notes");
I really wish ServiceStack would add an overload to the Generic Add method so I wouldn't have to specify the type in this way.
EDIT:
I decided to write an extension method to get the method I was initially looking for.
public static class RouteExtensions
{
public static ServiceStack.ServiceHost.IServiceRoutes Add<T>(
this ServiceStack.ServiceHost.IServiceRoutes route,
string restPath,
string verbs,
string summary,
string notes)
{
route.Add(typeof(T), restPath, verbs, summary, notes);
return route;
}
}
Now I can do this:
base.Routes
.Add<MyRequest>("/myrequest", "GET", "My Summary", "My Notes");