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

Handle unbound arguments from map and iterate calls on the JS client. #406

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions javascript/src/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ goog.requireType('ee.Geometry');
* @param {ee.Function} func The same argument as in ee.ComputedObject().
* @param {Object} args The same argument as in ee.ComputedObject().
* @param {string?=} opt_varName The same argument as in ee.ComputedObject().
* @param {boolean?=} opt_unbound The same argument as in ee.ComputedObject().
* @constructor
* @extends {ee.Element}
*/
ee.Collection = function(func, args, opt_varName) {
ee.Collection.base(this, 'constructor', func, args, opt_varName);
ee.Collection = function(func, args, opt_varName, opt_unbound) {
ee.Collection.base(this, 'constructor', func, args, opt_varName, opt_unbound);
ee.Collection.initialize();
};
goog.inherits(ee.Collection, ee.Element);
Expand Down
32 changes: 23 additions & 9 deletions javascript/src/computedobject.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ goog.requireType('ee.Function');
* and both 'func' and 'args' must be null. If all arguments are null, the
* object is considered an unnamed variable, and a name will be generated
* when it is included in an ee.CustomFunction.
* @param {?boolean=} opt_unbound Whether the object is unbound, i.e., called
* from a mapped or iterated function.
* @constructor
* @extends {ee.Encodable}
* @template T
*/
ee.ComputedObject = function(func, args, opt_varName) {
ee.ComputedObject = function(func, args, opt_varName, opt_unbound) {
// Constructor safety.
if (!(this instanceof ee.ComputedObject)) {
return ee.ComputedObject.construct(ee.ComputedObject, arguments);
Expand Down Expand Up @@ -76,6 +78,12 @@ ee.ComputedObject = function(func, args, opt_varName) {
* @protected
*/
this.varName = opt_varName || null;

/**
* Whether the computed object is an unbound variable.
* @type {boolean}
*/
this.unbound = !!opt_unbound;
};
goog.inherits(ee.ComputedObject, ee.Encodable);
// Exporting manually to avoid marking the class public in the docs.
Expand Down Expand Up @@ -153,14 +161,20 @@ ee.ComputedObject.prototype.encodeCloudValue = function(serializer) {
if (this.isVariable()) {
const name = this.varName || serializer.unboundName;
if (!name) {
// We are trying to call getInfo() or make some other server call inside a
// function passed to collection.map() or .iterate(), and the call uses
// one of the function arguments. The argument will be unbound outside of
// the map operation and cannot be evaluated. See the Count Functions case
// in customfunction.js for details on the unboundName mechanism.
// TODO(user): Report the name of the offending argument.
throw new Error(
'A mapped function\'s arguments cannot be used in client-side operations');
if (this.unbound) {
// We are trying to call getInfo() or make some other server call inside
// a function passed to collection.map() or .iterate(), and the call
// uses one of the function arguments. The argument will be unbound
// outside of the map operation and cannot be evaluated. See the Count
// Functions case in customfunction.js for details on the unboundName
// mechanism.
// TODO(user): Report the name of the offending argument.
throw new Error(`A mapped function's arguments (${
this.name()}) cannot be used in client-side operations.`);
} else {
throw new Error(
`Invalid cast to ${this.name()} from a client-side object.`);
}
}
return ee.rpc_node.argumentReference(name);
} else {
Expand Down
1 change: 1 addition & 0 deletions javascript/src/customfunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ ee.CustomFunction.variable = function(type, name) {
this.func = null;
this.args = null;
this.varName = name;
this.unbound = true;
};
klass.prototype = type.prototype;
return new klass(name);
Expand Down
9 changes: 6 additions & 3 deletions javascript/src/dictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,19 @@ ee.Dictionary = function(opt_dict) {
if (ee.Types.isRegularObject(opt_dict)) {
// Cast to a dictionary.
ee.Dictionary.base(this, 'constructor', null, null);
this.dict_ = /** @type {Object} */ (opt_dict);
this.dict_ = /** @type {!Object} */ (opt_dict);
} else {
if (opt_dict instanceof ee.ComputedObject && opt_dict.func &&
opt_dict.func.getSignature()['returns'] == 'Dictionary') {
// If it's a call that's already returning a Dictionary, just cast.
ee.Dictionary.base(this, 'constructor', opt_dict.func, opt_dict.args, opt_dict.varName);
ee.Dictionary.base(this, 'constructor', opt_dict.func, opt_dict.args, opt_dict.varName, opt_dict.unbound);
} else {
const unbound =
opt_dict instanceof ee.ComputedObject ? opt_dict.unbound : null;
// Delegate everything else to the server-side constructor.
ee.Dictionary.base(
this, 'constructor', new ee.ApiFunction('Dictionary'), {'input': opt_dict}, null);
this, 'constructor', new ee.ApiFunction('Dictionary'),
{'input': opt_dict}, null, unbound);
}
this.dict_ = null;
}
Expand Down
5 changes: 3 additions & 2 deletions javascript/src/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ goog.requireType('ee.Function');
* @param {ee.Function} func The same argument as in ee.ComputedObject().
* @param {Object} args The same argument as in ee.ComputedObject().
* @param {string?=} opt_varName The same argument as in ee.ComputedObject().
* @param {boolean?=} opt_unbound The same argument as in ee.ComputedObject().
* @constructor
* @extends {ee.ComputedObject}
*/
ee.Element = function(func, args, opt_varName) {
ee.Element.base(this, 'constructor', func, args, opt_varName);
ee.Element = function(func, args, opt_varName, opt_unbound) {
ee.Element.base(this, 'constructor', func, args, opt_varName, opt_unbound);
ee.Element.initialize();
};
goog.inherits(ee.Element, ee.ComputedObject);
Expand Down
2 changes: 1 addition & 1 deletion javascript/src/feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ ee.Feature = function(geometry, opt_properties) {
});
} else if (geometry instanceof ee.ComputedObject) {
// A custom object to reinterpret as a Feature.
ee.Feature.base(this, 'constructor', geometry.func, geometry.args, geometry.varName);
ee.Feature.base(this, 'constructor', geometry.func, geometry.args, geometry.varName, geometry.unbound);
} else if (geometry['type'] == 'Feature') {
// Try to convert a GeoJSON Feature.
var properties = geometry['properties'] || {};
Expand Down
2 changes: 1 addition & 1 deletion javascript/src/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ ee.Filter = function(opt_filter) {
}
} else if (opt_filter instanceof ee.ComputedObject) {
// Actual filter object.
ee.Filter.base(this, 'constructor', opt_filter.func, opt_filter.args, opt_filter.varName);
ee.Filter.base(this, 'constructor', opt_filter.func, opt_filter.args, opt_filter.varName, opt_filter.unbound);
this.filter_ = [opt_filter];
} else if (opt_filter === undefined) {
// A silly call with no arguments left for backward-compatibility.
Expand Down
2 changes: 1 addition & 1 deletion javascript/src/geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ ee.Geometry = function(geoJson, opt_proj, opt_geodesic, opt_evenOdd) {
'Setting the CRS, geodesic, or evenOdd flag on a computed Geometry ' +
'is not supported. Use Geometry.transform().');
} else {
ee.Geometry.base(this, 'constructor', geoJson.func, geoJson.args, geoJson.varName);
ee.Geometry.base(this, 'constructor', geoJson.func, geoJson.args, geoJson.varName, geoJson.unbound);
return;
}
}
Expand Down
2 changes: 1 addition & 1 deletion javascript/src/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ ee.Image = function(opt_args) {
{'value': opt_args});
} else {
// A custom object to reinterpret as an Image.
ee.Image.base(this, 'constructor', opt_args.func, opt_args.args, opt_args.varName);
ee.Image.base(this, 'constructor', opt_args.func, opt_args.args, opt_args.varName, opt_args.unbound);
}
} else {
throw Error('Unrecognized argument type to convert to an Image: ' +
Expand Down
2 changes: 1 addition & 1 deletion javascript/src/imagecollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ ee.ImageCollection = function(args) {
});
} else if (args instanceof ee.ComputedObject) {
// A custom object to reinterpret as an ImageCollection.
ee.ImageCollection.base(this, 'constructor', args.func, args.args, args.varName);
ee.ImageCollection.base(this, 'constructor', args.func, args.args, args.varName, args.unbound);
} else {
throw Error('Unrecognized argument type to convert to an ' +
'ImageCollection: ' + args);
Expand Down
2 changes: 1 addition & 1 deletion javascript/src/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ ee.List = function(list) {
ee.List.base(this, 'constructor', null, null);
this.list_ = /** @type {IArrayLike} */ (list);
} else if (list instanceof ee.ComputedObject) {
ee.List.base(this, 'constructor', list.func, list.args, list.varName);
ee.List.base(this, 'constructor', list.func, list.args, list.varName, list.unbound);
this.list_ = null;
} else {
throw Error('Invalid argument specified for ee.List(): ' + list);
Expand Down
2 changes: 1 addition & 1 deletion javascript/src/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ee.Number = function(number) {
ee.Number.base(this, 'constructor', null, null);
this.number_ = /** @type {number} */ (number);
} else if (number instanceof ee.ComputedObject) {
ee.Number.base(this, 'constructor', number.func, number.args, number.varName);
ee.Number.base(this, 'constructor', number.func, number.args, number.varName, number.unbound);
this.number_ = null;
} else {
throw Error('Invalid argument specified for ee.Number(): ' + number);
Expand Down
4 changes: 2 additions & 2 deletions javascript/src/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ ee.String = function(string) {
this.string_ = null;
if (string.func && string.func.getSignature()['returns'] == 'String') {
// If it's a call that's already returning a String, just cast.
ee.String.base(this, 'constructor', string.func, string.args, string.varName);
ee.String.base(this, 'constructor', string.func, string.args, string.varName, string.unbound);
} else {
ee.String.base(this, 'constructor', new ee.ApiFunction('String'), {'input': string}, null);
ee.String.base(this, 'constructor', new ee.ApiFunction('String'), {'input': string}, null, string.unbound);
}
} else {
throw Error('Invalid argument specified for ee.String(): ' + string);
Expand Down