Skip to content

Commit

Permalink
Safely navigate nullable objects
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinresol committed Jul 19, 2022
1 parent c99bc6e commit 5b26fc9
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 26 deletions.
6 changes: 3 additions & 3 deletions src/tink/web/macros/Arguments.hx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Arguments {
name: field.name,
type: field.type,
optional: field.meta.has(':optional'),
target: getArgTarget(paths, params, Drill(name, field.name), a.opt, pos),
target: getArgTarget(paths, params, Drill({name: name, nullable: field.meta.has(':optional')}, field.name), a.opt, pos),
}]);
case [name, _]:
AKSingle(
Expand Down Expand Up @@ -71,7 +71,7 @@ class Arguments {
static function stringifyArgAccess(access:ArgAccess) {
return switch access {
case Plain(name): name;
case Drill(name, field): '$name.$field';
case Drill({name: name}, field): '$name.$field';
}
}

Expand Down Expand Up @@ -101,7 +101,7 @@ typedef RouteArg = {

enum ArgAccess {
Plain(name:String);
Drill(name:String, field:String);
Drill(object:{name:String, nullable:Bool}, field:String);
}

enum ArgKind {
Expand Down
18 changes: 11 additions & 7 deletions src/tink/web/macros/Parameters.hx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class Parameters {
if (reserved(name)) p.reject('`$name` is reserved');
if (!types.exists(name)) p.reject('`$name` does not appear in the function argument list');
}

function isNullable(name:String) {
return types[name].match(TAbstract(_.get() => {name: 'Null', pack: []}, _));
}

function hasField(type:Type, name:String) {
return switch type {
Expand All @@ -40,7 +44,7 @@ class Parameters {
validate(name);
switch types[name].reduce() {
case TAnonymous(_.get() => {fields: fields}):
for(field in fields) add(Drill(name, field.name), LOCATION_FACTORY[pos](getParamName(field)));
for(field in fields) add(Drill({name: name, nullable: isNullable(name)}, field.name), LOCATION_FACTORY[pos](getParamName(field)));
case _:
p.reject('`$name` should be anonymous structure');
}
Expand All @@ -52,12 +56,12 @@ class Parameters {
case macro $i{name}.$field in $i{pos = 'query' | 'header' | 'body'}:
validate(name);
if(!hasField(types[name], field)) p.reject('`$name` does not has field "$field"');
add(Drill(name, field), LOCATION_FACTORY[pos](field));
add(Drill({name: name, nullable: isNullable(name)}, field), LOCATION_FACTORY[pos](field));

case macro $i{name}.$field = $i{pos = 'query' | 'header' | 'body'}[$v{(native:String)}]:
validate(name);
if(!hasField(types[name], field)) p.reject('`$name` does not has field "$field"');
add(Drill(name, field), LOCATION_FACTORY[pos](native));
add(Drill({name: name, nullable: isNullable(name)}, field), LOCATION_FACTORY[pos](native));

default:
p.reject('Invalid syntax for @:params, only the following are supported:
Expand Down Expand Up @@ -90,8 +94,8 @@ class Parameters {

static function conflictAccess(a1:ArgAccess, a2:ArgAccess) {
return switch [a1, a2] {
case [Plain(n1), Plain(n2)] | [Drill(n1, _), Plain(n2)] | [Plain(n1), Drill(n2, _)]: n1 == n2;
case [Drill(n1, f1), Drill(n2, f2)]: n1 == n2 && f1 == f2;
case [Plain(n1), Plain(n2)] | [Drill({name: n1}, _), Plain(n2)] | [Plain(n1), Drill({name: n2}, _)]: n1 == n2;
case [Drill({name: n1}, f1), Drill({name: n2}, f2)]: n1 == n2 && f1 == f2;
}
}

Expand All @@ -107,15 +111,15 @@ class Parameters {

public function byName(name:String):Array<ParamMapping> {
return params.filter(function(p) return switch p.access {
case Plain(n) | Drill(n, _): n == name;
case Plain(n) | Drill({name: n}, _): n == name;
});
}

public function get(access:ArgAccess):Option<ParamMapping> {
for(p in params)
switch [access, p.access] {
case [Plain(n1), Plain(n2)] if(n1 == n2): return Some(p);
case [Drill(n1, f1), Drill(n2, f2)] if(n1 == n2 && f1 == f2): return Some(p);
case [Drill({name: n1}, f1), Drill({name: n2}, f2)] if(n1 == n2 && f1 == f2): return Some(p);
case _:
}
return None;
Expand Down
2 changes: 1 addition & 1 deletion src/tink/web/macros/Paths.hx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class Path {
for(part in parts)
switch [access, part] {
case [Plain(n1), PCapture(Plain(n2))] if(n1 == n2): return Some(part);
case [Drill(n1, f1), PCapture(Drill(n2, f2))] if(n1 == n2 && f1 == f2): return Some(part);
case [Drill({name: n1}, f1), PCapture(Drill({name: n2}, f2))] if(n1 == n2 && f1 == f2): return Some(part);
case _:
}
return None;
Expand Down
4 changes: 2 additions & 2 deletions src/tink/web/macros/Proxify.hx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Proxify {
function val(p:PathPart)
return switch p {
case PCapture(Plain(name)): macro (($i{name} : tink.Stringly) : tink.url.Portion);
case PCapture(Drill(name, field)): throw 'TODO';
case PCapture(Drill({name: name}, field)): throw 'TODO';
case PConst(s): macro $s;
}

Expand Down Expand Up @@ -110,7 +110,7 @@ class Proxify {
var writer = w.generator;
macro ${writer}($i{name});

case Flat(Drill(name, field), type):
case Flat(Drill({name: name}, field), type):
throw "TODO";

case Object(TAnonymous([])):
Expand Down
17 changes: 10 additions & 7 deletions src/tink/web/macros/Route.hx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Route {
for(field in fields)
switch field.target {
case ATParam(kind):
arr.push({id: id++, access: Drill(arg.name, field.name), type: field.type, optional: optional || field.optional, kind: kind});
arr.push({id: id++, access: Drill({name: arg.name, nullable: arg.optional}, field.name), type: field.type, optional: optional || field.optional, kind: kind});
case _: // skip
}
case _: // skip
Expand Down Expand Up @@ -209,12 +209,15 @@ abstract Payload(Pair<Position, Array<{id:Int, access:ArgAccess, type:Type, opti
add(query, macro $i{name});
case [Plain(name), PKHeader(_)]:
add(header, macro $i{name});
case [Drill(name, field), PKBody(Some(_))]:
add(body, macro $p{[name, field]});
case [Drill(name, field), PKQuery(_)]:
add(query, macro $p{[name, field]});
case [Drill(name, field), PKHeader(_)]:
add(header, macro $p{[name, field]});
case [Drill({name: name, nullable: nullable}, field), PKBody(Some(_))]:
final access = macro $p{[name, field]};
add(body, nullable ? macro ($i{name} == null ? null : $access) : access);
case [Drill({name: name, nullable: nullable}, field), PKQuery(_)]:
final access = macro $p{[name, field]};
add(query, nullable ? macro ($i{name} == null ? null : $access) : access);
case [Drill({name: name, nullable: nullable}, field), PKHeader(_)]:
final access = macro $p{[name, field]};
add(header, nullable ? macro ($i{name} == null ? null : $access) : access);
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/tink/web/macros/Routing.hx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class Routing {
case PCapture(Plain(name)):
captured[name] = true;
macro $i{name};
case PCapture(Drill(name, field)):
case PCapture(Drill({name: name}, field)):
throw "TODO";
}

Expand Down Expand Up @@ -519,13 +519,13 @@ class Routing {
plain(name, macro __query__);
case [Plain(name), PKHeader(_)]:
plain(name, macro __header__);
case [Drill(name, field), PKBody(None)]:
case [Drill({name: name}, field), PKBody(None)]:
drill(name, field, macro __body__, true);
case [Drill(name, field), PKBody(Some(_))]:
case [Drill({name: name}, field), PKBody(Some(_))]:
drill(name, field, macro __body__);
case [Drill(name, field), PKQuery(_)]:
case [Drill({name: name}, field), PKQuery(_)]:
drill(name, field, macro __query__);
case [Drill(name, field), PKHeader(_)]:
case [Drill({name: name}, field), PKHeader(_)]:
drill(name, field, macro __header__);
}
}
Expand Down
4 changes: 4 additions & 0 deletions tests/Fake.hx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ class Fake {
@:post public function optional(body: { foo:String, ?bar: Int })
return {bar:body.bar};

@:params(nullableValue = query)
@:post public function optionalQuery(?nullableValue: { foo:String })
return {foo: nullableValue == null ? null : nullableValue.foo};

@:restrict(user.id == a)
@:sub('/sub/$a/$b')
public function sub(a, b) {
Expand Down
12 changes: 11 additions & 1 deletion tests/ProxyTest.hx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,17 @@ class ProxyTest {
public function int() {
proxy.int(1)
.next(function (o) {
o == 1;
asserts.assert(o == 1);
return Noise;
})
.handle(asserts.handle);
return asserts;
}

public function optionalQuery() {
proxy.optionalQuery()
.next(function (o) {
asserts.assert(o.foo == null);
return Noise;
})
.handle(asserts.handle);
Expand Down

0 comments on commit 5b26fc9

Please sign in to comment.