Files
lime/tools/haxelib/Validator.hx
2017-05-18 11:20:53 -07:00

177 lines
4.7 KiB
Haxe

/*
* Copyright (C)2005-2016 Haxe Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package haxelib;
import haxe.ds.Option;
#if macro
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.macro.Context;
using haxe.macro.Tools;
#end
typedef Validatable = {
function validate():Option<String>;
}
class Validator {
#if macro
static var ARG = 'v';
var pos:Position;
var IARG:Expr;
function new(pos) {
this.pos = pos;
IARG = macro @:pos(pos) $i{ARG};
}
function doCheck(t:Type, e:Expr) {
var ct = t.toComplexType();
return
macro @:pos (function ($ARG : $ct) ${makeCheck(t)})($e);
}
function isAtom(s:String)
return switch s {
case 'String', 'Int', 'Bool', 'Float': true;
default: false;
}
function enforce(type:String)
return
macro @:pos(pos) if (!Std.is($i{ARG}, $i{type})) throw $v{'$type expected'};
function rename(e:Expr)
return switch e {
case macro $i{name} if (name == '_'): IARG;
default: e.map(rename);
}
function makeCheck(t:Type):Expr
return
switch Context.follow(t) {
case TAnonymous(_.get().fields => fields):
var block:Array<Expr> = [
for (f in fields)
if (f.kind.match(FVar(AccNormal, _)))
{
var name = f.name;
var rec = doCheck(f.type, macro @:pos(pos) $IARG.$name);
if (f.meta.has(':requires')) {
var body = [];
for (m in f.meta.get())
if (m.name == ':requires')
for (p in m.params)
switch p {
case macro $msg => $p:
body.push(rename(
macro @:pos(pos) if (!$p) throw $msg
));
default:
Context.error('Should be "<message>" =>" <condition>', p.pos);
}
//{
//p = rename(p);
//cond = macro @:pos(pos) $p && $cond;
//}
var t = f.type.toComplexType();
rec = macro @:pos(pos) {
$rec;
(function($ARG : $t) $b{body})($IARG.$name);
}
}
if (f.meta.has(':optional')) {
rec = macro @:pos(pos) if (Reflect.hasField($IARG, $v{name}) && $IARG.$name != null) $rec;
}
else
rec = macro @:pos(pos)
if (!Reflect.hasField($IARG, $v{name}))
throw ("missing field " + $v{name});
else
$rec;
rec;
}
];
block.unshift(
macro @:pos(pos) if (!Reflect.isObject($IARG)) throw 'object expected'
);
macro @:pos(pos) $b{block};
case _.toString() => atom if (isAtom(atom)):
enforce(atom);
case TInst(_.get().module => 'Array', [p]):
macro @:pos(pos) {
${enforce('Array')};
for ($IARG in $IARG)
${doCheck(p, IARG)};
}
case TAbstract(_.get() => { from: [ { t: t, field: null } ] }, _):
makeCheck(t);
case TAbstract(_.get() => a, _) if (a.meta.has(':enum')):
var name = a.module + '.' + a.name;
var options:Array<Expr> = [
for (f in a.impl.get().statics.get())
if (f.kind.match(FVar(_, _)))
macro @:pos(pos) $p{(name+'.'+f.name).split('.')}
];
macro if (!Lambda.has($a { options }, $IARG)) throw 'Invalid value ' + $IARG + ' for ' + $v { a.name };
case TAbstract(_.get() => a, _):
macro @:pos(pos) switch ($IARG : haxelib.Validator.Validatable).validate() {
case Some(e): throw e;
case None:
}
case TDynamic(k):
var checker = makeCheck(k);
var ct = k.toComplexType();
macro @:pos(pos) {
if (!Reflect.isObject($i{ARG})) throw 'object expected';
for (f in Reflect.fields($i{ARG})) {
var $ARG:$ct = Reflect.field($i{ARG}, f);
$checker;
}
}
case v:
throw t.toString();
}
#end
macro static public function validate(e:Expr)
return
new Validator(e.pos).doCheck(Context.typeof(e), e);
}