I'm using Cloudkit JS to save data to a public database. Its easy to do when the fields are all strings. I'm stuck now trying to figure out how to save data when the field type is a CLLocation. Somehow I need to structure the JavaScript to send both latitude and longitude values.
See the ??? in the code example below;
var new record = { recordType: "Thing",
fields: { name: { value: "My Name" },
description: { value: "My Description" },
location: { ??? }
}
};
Does anyone know how to take the lat and long coordinates and represent them in the code above?
Try passing it like this:
fields: {
location: { value: { latitude: lat, longitude: lng }, type: "LOCATION" }
}
lat and lng are Doubles, not Strings
Solved
For a first ever macro to write this wasnt the easiest. But I learned a lot, much kudo's to Gama11 who pointed me in the right direction, and the coreteam for such a thing of beauty: Haxe.
And I even added some slick doc field strings, so you get nice info during autocompletion.
Main.hx
var e1:Either<String, Int, Bool> = Either3._1('test');
var e2:Either<String, Int, Bool> = Either3._2(1);
var e3:Either<String, Int, Bool> = Either3._3(true);
var error:Either<String, Int, Bool> = Either3._3('Bool expected, but got a String this will give an error');
Either.hx
package;
#:genericBuild(EitherMacro.build())
class Either<Rest> {}
/*#:genericbuild only works on classes, but
can still override the class with an enum. Funky. */
EitherMacro.hx
package;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
class EitherMacro {
static var eitherTypes = new Map<Int,Bool>();
static function build():ComplexType {
return switch (Context.getLocalType()) {
case TInst(_.get() => {name: "Either"}, params):
buildEitherEnum(params);
default:
throw false;
}
return macro:Dynamic;
}
static function buildEitherEnum(params:Array<Type>):ComplexType {
var numParams = params.length;
var name='Either$numParams';
if (!eitherTypes.exists(numParams)){
Context.defineType(defineType(name, params));
eitherTypes[numParams] = true;
}
return TPath({pack: [], name: name, params: [for (t in params) TPType(t.toComplexType())]});
}
private static inline function defineType(name:String, params:Array<Type>){
var typeParams:Array<TypeParamDecl> = [];
var typeStrings:Array<String>=[];
var numParams = params.length;
var fields:Array<Field>=[];
for (i in 0...numParams) {
var t=i+1;
typeStrings.push(params[i].toString());
}
var constDocStr=typeStrings.join(',');
for (i in 0...numParams) {
var t=i+1;
var typeString:String=typeStrings[i];
typeParams.push({name:'T$t'});
fields.push(
{
name: '_$t',
pos: Context.currentPos(),
doc: 'from $name<$constDocStr> _$t(v: $typeString)',
kind:FFun({
ret: null,
params: [{name:'T$t'}],
expr: null,
args: [
{
name: 'v',
type: TPath(
{
name:'T$t',
params:[],
pack:[]
}
)
}
]
}
)
}
);
}
var docStr:String="Either represents values which are either of type ";
for(k in 0...typeStrings.length){
if(k!=typeStrings.length-1){
docStr+=typeStrings[k]+" or ";
} else {
docStr+=typeStrings[k]+".";
}
}
return {
pack:[],
name:name,
pos:Context.currentPos(),
doc:docStr,
isExtern: false,
meta:null,
kind:TDEnum,
fields:fields,
params:typeParams
}
}
}
#end
Debugging your macro's the easy way
usage of -D dump=pretty dumps typed AST in dump subdirectory using prettified mode. The output from dump=pretty is almost indistuingishable from regular Haxe code. When errors appear, you find iin the root of the dump directory a file called 'decoding_error.txt'. Its contents might look like this:
{
doc: null
fields: null <- expected value
isExtern: null
kind: null <- expected value
meta: null
name: null <- expected value
pack: null <- expected value
params: null
pos: null <- expected value
}
line 3: expected value
line 5: expected value
line 7: expected value
line 8: expected value
line 10: expected value
This made it much easier for me to debug. But the even better way, is way simple... To debug the easiest way, go to your macrofile (in my case EitherMacro.hx) and do
class EitherMacro{
public static function build(){
var fields=Context.getBuildFields();
var type=Context.getLocalType();
trace(type);
for(f in fields){
trace(f);
}
// your other code
/*
If you use #:build)() instead of #:genericbuild
to debug. Make sure the buildfunction returns
Array<Field> and put at the last line
return Context.getBuildFields();
if you use #:genericbuild you must return
ComplexType, and you can add as the line
return macro:Dynamic; if you have no working return yet.
*/
}
}
the output might look like this:
source/EnumBuilder2.hx:18: TEnum(SomeEnum,[TInst(SomeEnum.T1,[]),TInst(SomeEnum.T2,[]),TInst(SomeEnum.T3,[])])
source/EnumBuilder2.hx:20: {name: _1, doc: null, pos: #pos(source/SomeEnum.hx:4: characters 5-14), access: [], kind: FFun({ret: null, params: [], expr: null, args: [{name: v, opt: false, meta: [], type: TPath(<...>), name_pos: #pos((unknown)), value: null}]}), meta: [], name_pos: #pos(source/SomeEnum.hx:4: characters 5-7)}
source/EnumBuilder2.hx:20: {name: _2, doc: null, pos: #pos(source/SomeEnum.hx:5: characters 5-14), access: [], kind: FFun({ret: null, params: [], expr: null, args: [{name: v, opt: false, meta: [], type: TPath(<...>), name_pos: #pos((unknown)), value: null}]}), meta: [], name_pos: #pos(source/SomeEnum.hx:5: characters 5-7)}
source/EnumBuilder2.hx:20: {name: _3, doc: null, pos: #pos(source/SomeEnum.hx:6: characters 5-14), access: [], kind: FFun({ret: null, params: [], expr: null, args: [{name: v, opt: false, meta: [], type: TPath(<...>), name_pos: #pos((unknown)), value: null}]}), meta: [], name_pos: #pos(source/SomeEnum.hx:6: characters 5-7)}
Another good idea with #:genericbuild(), is to first constructed an enum by hand(or whatever type) and after that trace it using a #:genericbuild, or if you got too much errors using #:build. Then you can copy those trace outputs and try use that code to craft the AST in the macro. This will seriously speedup your development, especially in case of complicated macro's. Almost mindlessly ;-)
Your macro has never run.
Replace your build() function with the following to verify
static function build():ComplexType {
trace('build');
return macro:Dynamic;
}
I suppose #:genericBuild only works for class
I have a schema.graphqls that looks like this:
type House {
id: ID!
rooms: Int!
address: String!
owner: Owner
}
type Owner: {
name: String!,
age: Int!
}
and a complementing mongoose schema:
export default class House {
static schema = {
rooms: Number
address: String,
owner: {
type : {
name: String,
age: Number
},
required: false
}
};
}
and I have an object in my mongodb looking like this (notice owner is missing intentionally):
ObjectId("xxx") {
rooms: 3,
address: "the street"
}
I'm trying to retrieve this document, the owner subdocument is missing (which is fine, its not mandatory).
The mongoose result fills this missing subdocument with undefined attributes,
ObjectId("xxx") {
rooms: 3,
address: "the street"
owner : {
name: undefined
age: undefined
}
which fails the schema validations (since indeed name and age are mandatory, if subdocument exists).
the actual error i'm getting is:
Resolve function for "House.owner" returned undefined
Could you possibly point me to whatever i'm doing wrong here?
thanks in advance
Following a direction from #Neil Lunn, I realised the problem is in the mongoose schema,
which led me to add required: false - which wasn't enough, but after adding also default: null voila.
problem solved. error gone.
final mongoose schema, to whom it may be of interest:
export default class House {
static schema = {
rooms: Number
address: String,
owner: {
type : {
name: String,
age: Number
},
required: false,
default: null
}
};
}
I want to use an rule like, but I'm having some issues to fullfill the "is" rule for the when condition on Joi validation library.
let schema = {
field1: Joi.array().items(Joi.string().valid('v1', 'v2')),
field2: Joi.when("field1", {
is: // if field1 contains at least 'v1',
then: Joi.object().keys(...),
otherwise: Joi.forbidden()
}
}
You can use array.items by listing all allowed types. If a given type is .required() then there must be a matching item in the array:
joi API reference
let schema = {
field1: Joi.array().items(Joi.string().valid('v1', 'v2')),
field2: Joi.when("field1", {
is: Joi.array().items(Joi.string().valid('v1').required(), Joi.string().valid('v2'))
then: Joi.object().keys(...),
otherwise: Joi.forbidden()
}
}
I have a reduce function like this:
ops = rqOps.reduce (p, { commit: id: cid, type: type }, idx, arr) ->
# Do stuff here
p
, {}
which works fine, but now the name of the second argument compiles to _arg. How can I give it a different name? I've tried several different approaches like arg = { commit: id: cid, type: type } and { commit: id: cid, type: type } : arg and { commit: id: cid, type: type } = arg but nothing compiles to the intended result. What's wrong with my syntax?
Why do you care what the second argument is called? Your object destructuring means that you wouldn't work with that argument at all, you'd just work with cid and type instead. The _arg name and even its existence is subject to change and none of your business.
For example, if you have this:
rqOps = [
{ commit: { id: 1, type: 2 } }
{ commit: { id: 2, type: 4 } }
]
ops = rqOps.reduce (p, { commit: id: cid, type: type }, idx, arr) ->
console.log(cid, type)
p
, { }
then you'll get 1, 2 and 2, 3 in the console. If you want the whole second argument then give it a name and unpack it inside the iterator function:
ops = rqOps.reduce (p, arg, idx, arr) ->
{ commit: id: cid, type: type } = arg
#...