I am writing a client that makes requests to the DA Ledger. I am following the advice I received in a previous post Doing CRUD on the DA Ledger through a gRPC client.
I need to run the 'GetTransactions' rpc. Doing so requires GetTransactionsRequest object. The GetTransactionsRequest object has a required property called 'filter' which is of type TransactionFilter. I am having trouble creating a transaction filter to meet my needs. The .proto file for it is:
// Used for filtering Transaction and Active Contract Set streams.
// Determines which on-ledger events will be served to the client.
message TransactionFilter {
// Keys of the map determine which parties' on-ledger transactions are being queried.
// Values of the map determine which events are disclosed in the stream per party.
// At the minimum, a party needs to set an empty Filters message to receive any events.
// Required
map<string, Filters> filters_by_party = 1;
}
the one and only field of 'filters_by_party' is required.
Setting this field in php requires the following function:
/**
* Keys of the map determine which parties' on-ledger transactions are being queried.
* Values of the map determine which events are disclosed in the stream per party.
* At the minimum, a party needs to set an empty Filters message to receive any events.
* Required
*
* Generated from protobuf field <code>map<string, .com.digitalasset.ledger.api.v1.Filters> filters_by_party = 1;</code>
* #param array|\Google\Protobuf\Internal\MapField $var
* #return $this
*/
public function setFiltersByParty($var)
{
$arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Com\Digitalasset\Ledger\Api\V1\Filters::class);
$this->filters_by_party = $arr;
return $this;
}
The php function for setting up a values in a mapFiled object is:
/**
* Assign the element at the given key.
*
* This will also be called for: $arr[$key] = $value
*
* #param object $key The key of the element to be fetched.
* #param object $value The element to be assigned.
* #return void
* #throws ErrorException Invalid type for key.
* #throws ErrorException Invalid type for value.
* #throws ErrorException Non-existing key.
*/
public function offsetSet($key, $value)
{
$this->checkKey($this->key_type, $key);
switch ($this->value_type) {
case GPBType::SFIXED32:
case GPBType::SINT32:
case GPBType::INT32:
case GPBType::ENUM:
GPBUtil::checkInt32($value);
break;
case GPBType::FIXED32:
case GPBType::UINT32:
GPBUtil::checkUint32($value);
break;
case GPBType::SFIXED64:
case GPBType::SINT64:
case GPBType::INT64:
GPBUtil::checkInt64($value);
break;
case GPBType::FIXED64:
case GPBType::UINT64:
GPBUtil::checkUint64($value);
break;
case GPBType::FLOAT:
GPBUtil::checkFloat($value);
break;
case GPBType::DOUBLE:
GPBUtil::checkDouble($value);
break;
case GPBType::BOOL:
GPBUtil::checkBool($value);
break;
case GPBType::STRING:
GPBUtil::checkString($value, true);
break;
case GPBType::MESSAGE:
if (is_null($value)) {
trigger_error("Map element cannot be null.", E_USER_ERROR);
}
GPBUtil::checkMessage($value, $this->klass);
break;
default:
break;
}
$this->container[$key] = $value;
}
How do I, for example, set up the parties name 'dealer1' and 'dealer2' as my parties for filters_by_party. I tried the following code:
$parties= new Google\Protobuf\Internal\MapField(Google\Protobuf\Internal\GPBType::STRING,Google\Protobuf\Internal\GPBType::MESSAGE);
$parties->offsetSet(0,"dealer1");
$parties->offsetSet(1,"dealer2");
results in the following error:
PHP Fatal error: Given value is not message. in /home/vantage/damlprojects/loaner_car/php/ledger_client.php on line 85
I don't understand why a 'message'
is required by the filter_by_party 'set' function. I don't know how to write the dealer name in the form of a 'meessage'. It seems doing something that should be simple is very complicated. What is the correct way to set up the input $var to the 'setFiltersByParty' function?
Maybe you can find some information here:
https://developers.google.com/protocol-buffers/docs/reference/php-generated#fields
For map field, I guess it would be something looks like:
$m->getFiltersByParty()["string"] = new Filters();
The code for setting up 'dealer1' and dealer2' should look as follows:
$parties= new Google\Protobuf\Internal\MapField(Google\Protobuf\Internal\GPBType::STRING,
Google\Protobuf\Internal\GPBType::MESSAGE, "Com\Digitalasset\Ledger\Api\V1\Filters");
$partyIdentifier = new Com\Digitalasset\Ledger\Api\V1\Identifier();
$partyIdentifier->setPackageId($the_package_id); //last one from ListPackages()
$partyIdentifier->setEntityName("<Module>"); //from DAML code
$partyIdentifier->setModuleName("<Template>"); //from DAML code
$partyInclusiveFilters = new Com\Digitalasset\Ledger\Api\V1\InclusiveFilters();
$partyInclusiveFilters->setTemplateIds(array($partyIdentifier));
$partyFilters = new Com\Digitalasset\Ledger\Api\V1\Filters();
$partyFilters->setInclusive($partyInclusiveFilters);
$parties->offsetSet("dealer1",$partyFilters);
$parties->offsetSet("dealer2",$partyFilters);
It is important to set the third parameter of the MapField constructor to "Com\Digitalasset\Ledger\Api\V1\Filters". As the protobuf file suggests:
message TransactionFilter {
// Keys of the map determine which parties' on-ledger transactions are being queried.
// Values of the map determine which events are disclosed in the stream per party.
// At the minimum, a party needs to set an empty Filters message to receive any events.
// Required
map<string, Filters> filters_by_party = 1;
}
The filters_by_party is MapField that has a value_type of 'Filter'. 'Filter' is a type of message (see your transaction_filter.proto file), therefore the MapField constructor needs to know what type of message it is.
The dealer name is not being written in the form of a "message" but rather it is a key_value in the the 'filters_by_party' MapField that corresponds to a 'Filter' object. And a 'Filter' is a type of message. I guess 'simple' is a relative term.
This is how Encoding Object Example is done in OpenApi
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
requestBody:
content:
multipart/mixed:
schema:
type: object
properties:
id:
# default is text/plain
type: string
format: uuid
address:
# default is application/json
type: object
properties: {}
historyMetadata:
# need to declare XML format!
description: metadata in XML format
type: object
properties: {}
profileImage:
# default is application/octet-stream, need to declare an image type only!
type: string
format: binary
encoding:
historyMetadata:
# require XML Content-Type in utf-8 encoding
contentType: application/xml; charset=utf-8
profileImage:
# only accept png/jpeg
contentType: image/png, image/jpeg
headers:
X-Rate-Limit-Limit:
description: The number of allowed requests in the current period
schema:
type: integer
I'm trying to achive the same thing but with swagger-php.
What I don't know is how to pass encodings in #OA\MediaType to encode the test property as multipart/form-data because by default is encoded as application/json
EX:
* #OA\Post(
* path="/admin/test",
* summary="Create new Test",
* description="Will attempt to create a new Test",
* tags={"Admin Test"},
* #OA\RequestBody(
* #OA\MediaType(
* mediaType="multipart/form-data",
* encoding={}
* #OA\Schema(
* type="object",
* #OA\Property(
* property="test",
* type="object",
* description="test"
* ref="#/components/schemas/MyTestSchema"
* )
* )
* )
They have some examples here:
https://github.com/zircote/swagger-php/tree/master/Examples
but I didn't found any example regarding encoding
Inside here the field is defined
https://github.com/zircote/swagger-php/blob/master/src/Annotations/MediaType.php
/**
* A map between a property name and its encoding information.
* The key, being the property name, must exist in the schema as a property.
* The encoding object shall only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
*/
public $encoding = UNDEFINED;
I've tried encoding={"recommended"={"contentType"="multipart/form-data"}} but it's useless.
I think the only solution is putting all the fields:
* #OA\Post(
* path="/admin/test",
* summary="Create new Test",
* description="Will attempt to create a new Test",
* tags={"Admin Test"},
* #OA\RequestBody(
* #OA\MediaType(
* mediaType="multipart/form-data",
* #OA\Schema(
* #OA\Property(
* property="test[name]",
* type="string",
* description="name"
* ),
* #OA\Property(
* property="test[desc]",
* type="string",
* description="description"
* )
* )
* )
* )
I am having difficulty understanding how exceptions are handled when code is fetched dynamically via AJAX and executed via eval. With clientside javascript, it is rather simple, if I have a piece of code such as this
var j = 'some string';
j.propA.x++;
this will raise an exception because propA, which is of type undefined does not have an x. Furthermore, the exception raised is very easy to understand.
Now lets put the above code in a text file, lets call it test.js, and store it on the server. Now lets load it dynamically with Ajax. I am using the following code to load it dynamically
dojo.xhrGet({
url: 'load.php',
handleAs: "javascript",
content : {
fileName : 'test.js'
},
load: function(returnValue) {
/*Do Something*/
},
error: function(errorMessage) {
/*Report Error*/
}
});
Here is a very basic php script for loading the file and returning it as javascript code
<?php
$fileName = $_GET['fileName'];
$handle = fopen($fileName , 'r');
$script = fread($handle, filesize($fileName));
fclose($handle);
echo $script;
?>
In the above dojo.xhrGet call, the error property can be set to a function to display the error message, here is an example of some of the many ways this can be done.
error: function(errorMessage) {
console.error(errorMessage);
console.error(errorMessage.arguments);
console.error(errorMessage.message);
console.error(errorMessage.stack);
console.error(errorMessage.type);
}
Below is an example of the output. Although this output is for a different problem, it highlights how incomprehensible it is:
Cannot read property 'x' of undefined
TypeError: Cannot read property 'x' of undefined
at eval at <anonymous> (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:3088)
at Object.load (http://192.168.1.8/easel.js:166:6)
at http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:89998
at _144 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36518)
at _142 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36328)
at [object Object].<anonymous> (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36994)
at _144 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36780)
at _142 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36328)
at [object Object].<anonymous> (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36994)
at Object.resHandle (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:92730)
non_object_property_load
I am assuming dojo.xd.js:14 is the line where the eval statement is.
If one knows what they are looking for, the above might suffice. However, is there an easier, or at least a more productive way to deal with exceptions arising in eval?
Here is a somewhat similar question.
Phikin provided a good solution to this problem below so I gave him the bounty. Using his solution, I got an output which looked something like this (I cut it down a bit)
ReferenceError in JS Code detected: (url: module.require.php?module=MainMenu.Bg_S)
easel.js:211Error Message: ReferenceError: apple is not defined
easel.js:213(function(){
return function(args){
dojo.require("Shape");
Module.assert('MainMenu_V');
/**
* The rectangular background of the Main View
* #property MainMenuBg_S
* #type Shape
**/
new Shape({
/**
* Unique descriptive name used when later accessing this shape via '$$()'
* #param name
* #type String
**/
name : 'MainMenu.Bg_S' ,
/**
* Left side of this rectangle
* #param x
* #type Number
**/
x : $$('MainMenu_V').x ,
/**
* Top of this rectangle
* #param y
* #type Number
**/
y : $$('MainMenu_V').y ,
/**
* Width of this rectangle
* #param w
* #type Number
**/
w : $$('MainMenu_V').w ,
/**
* Height of this rectangle
* #param h
* #type Number
**/
h : $$('MainMenu_V').h ,
/**
* Type of this Shape
* #param h
* #type Number
**/
type : shapeType.RECTANGLE ,
/**
* Generate function which contains all the graphics instructions, as well as the contexts
* to preload and initialize. This is currently under development. Backgrounds should NEVER
* have mouse events associated with them as a redraw of a background implies a redraw of
* every single displayObject infront of the background.
* #param generate
* #type method
**/
generate : function (){
var x = this.x << 0 , y = this.y << 0 , h = this.h << 0 , w = this.w << 0 , a = this.a;
this.graphics(contextID.LEAVE).lf([hsl(180,100,60,0.9),hsl(180,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.ENTER).lf([hsl(135,100,40,0.9),hsl(135,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.CLICK).lf([hsl(90,100,40,0.9),hsl(90,50,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.RCLICK).lf([hsl(90,110,40,0.9),hsl(80,60,20,0.45)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.DBLCLICK).lf([hsl(45,100,40,0.9),hsl(45,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.DBLRCLICK).lf([hsl(10,100,40,0.9),hsl(10,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.LPRESS).lf([hsl(110,25,40,0.9),hsl(110,25,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.RPRESS).lf([hsl(110,50,40,0.9),hsl(110,50,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.SCROLL).lf([hsl(110,50,40,0.9),hsl(110,50,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
if (debugFlags.BOUNDINGBOX()){
this.graphics(contextID.ENTER).ss(2).s(rgba(0,255,0,a)).dr(this.boundingBox.softBounds.L +4<<0, this.boundingBox.softBounds.T +4<<0, this.boundingBox.softBounds.w-8<<0 , this.boundingBox.softBounds.h-8<<0).es();
this.graphics(contextID.ENTER).ss(2).s(rgba(255,0,0,a)).dr(this.boundingBox.bounds.L +4<<0, this.boundingBox.bounds.T +4<<0, this.boundingBox.bounds.w-8<<0 , this.boundingBox.bounds.h-8<<0).es();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[0].x+4 , this.boundingBox.points[0].y+4 , 4).ef();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[1].x-8 , this.boundingBox.points[1].y+4 , 4).ef();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[2].x-8 , this.boundingBox.points[2].y-8 , 4).ef();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[3].x+4 , this.boundingBox.points[3].y-8 , 4).ef();
}
},
/**
* Arguments to pass to the mouse initialization function. These will get mixed in (via
* dojo.mixin) to the mouse object. To increase performance, the signalOrderIn has been set to
* NOHIT. This will limit the number of redraws (remember background redraws are extremely
* expensive as they require redrawing everything in the container). The signalOrderOut is
* then set to BLOCK to prvent anything behind the background from receiving mouse signals
* (this is actually unecessary as the only think behind the background is, and always should
* be, the container, which itself has signalOrderIn and signalOrderOut set to NOHIT and BLOCK
* respectively).
* #param mouse
* #type Object
**/
mouse : {
_signalOrderIN : signalFlags.NOHIT ,
_signalOrderOUT : signalFlags.BLOCK
} ,
/**
* All views are initially loaded via Ajax. Generally, views do not have any preconditions, beyond
* that the stage be present. They can, however, and generally do, have modules they require. These
* are called after this view has been created and loaded (load() function call). They are called
* in the order of the sub arrays. In the example below:
* [[A , B , C , D , E , F , G]]
* The 7 modules are requested in that order, but, due to Ajax, they can be loaded in any order.
* In the below example, on the other hand:
* [[A] , [B , C , D , E , F , G]]
* Modules B-G depend on module A, therefore, module A is ordered to be loaded first.
* #property providedModules
* #type Array[Array[String]]
* #protected
**/
providedModules : [[]] ,
/**
* Carries out all the initializations when loading the module
* #method load
* #protected
**/
load : function (){
0/apple;
$$('MainMenu_V').addChild(this);
} ,
/**
* Carries out all memory deallocation when leaving the module (generally only necessary if modules
* were loaded but not added to stage as in the case with cached bitmaps)
* #method leave
* #protected
**/
leave : function (){
}
});
$$('MainMenu.Bg_S')._code="dojo.require(\"Shape\");...";
};
}());
easel.js:217Error triggered by: function (_2bd){return err.call(args,_2bd,_2b7);}
easel.js:220XHR Object:
easel.js:221
Object
args: Object
handleAs: "javascript"
query: null
url: "module.require.php?module=MainMenu.Bg_S"
xhr: XMLHttpRequest
__proto__: Object
easel.js:222Error Object:
easel.js:223
ReferenceError
arguments: Array[1]
message: "—"
stack: "—"
type: "not_defined"
__proto__: Error
dojo.xd.js:14
ReferenceError
arguments: Array[1]
message: "—"
stack: "—"
type: "not_defined"
__proto__: Error
dojo.xd.js:14
ReferenceError
arguments: Array[1]
message: "—"
stack: "—"
type: "not_defined"
__proto__: Error
The only thing it's missing, that I need, is the ability to indicate what line the problem occurred.
Here is a snipped that detectes non-network related errors from an xhr-get request and outputs some information about it in the console.
There is an extra isEvalError() function that goes through all eval-error types... which I am not really proud of. A nicer way could be by getting the parent object of the errorMessage sub-classes.
I think you can ditch isEvalError() generally, because there shouldn´t be any other error possible in this block.
function isEvalError(errorMessage){
return errorMessage.name == "RangeError" ||
errorMessage.name == "ReferenceError" ||
errorMessage.name == "SyntaxError" ||
errorMessage.name == "URIError" ||
errorMessage.name == "TypeError";
}
var foo = dojo.xhrGet({
url: 'stacko.js',
handleAs: "javascript",
load: function(returnValue) {
console.log("load: "+returnValue);
},
error: function(errorMessage,ioargs) {
//request worked fine, this must be a non-network related error
if(ioargs.xhr.readyState == 4 && ioargs.xhr.status == 200) {
if(isEvalError(errorMessage)){
//show eval-error, url request & the JS code that causes the exception
//eval-error types: RangeError,ReferenceError,SyntaxError, URIError, TypeError
console.error(errorMessage.name+" in JS Code detected: (url: "+ioargs.url+")")
console.error("Error Message: "+ errorMessage);
console.error(ioargs.xhr.responseText);
}
//a little reflection - if u want to know who triggered this error
//(although in this case the output is not very helpful )
console.error("Error triggered by: "+arguments.callee.caller.toString());
//last but not least log the error & the xhr-request object for more information
console.error("XHR Object:");
console.error(ioargs);
console.error("Error Object:");
console.error(errorMessage);
}
}
});
It really depends on what you mean "productive way to deal with exceptions". If all you need to do is view the exception contents, a simple
console.log(errorMessage);
will allow you to effortlessly inspect the error object in decent browser like Chrome or Firefox (with Firebug). (Instead of forcing you to do a ton of console.log statements)
An annoying thing about Dojo exceptions inside asynchronous code is that they are always caught and handled so most browser debuggers ignore them. A notable exception to this rule is Chrome, where you can tell the debugger to pause on all exceptions.
BTW: I don't see how Javascript exceptions and Dojo have anything to do with PHP in this case, since they occur on the client side and there is nothing the server can do about them. Also, what the hell are you doing by sending Javascript code in the AJAX? Most of the time a client does a request it will be for data, in plain text, JSON or XML.