Search code examples
c++validationif-statementlogicv8

How to effectively use if and else for a filtering construct?


To parse function parameters I get from JavaScript, I need to perform a lot of checks. For example a function might expect an object as parameter that looks like this in JavaScript.

{
    Fullscreen: [ 'bool',     false         ],
    Size:       [ 'Vector2u', 800, 600      ],
    Title:      [ 'string',   'Hello World' ],
    // more properties...
}

In C++ I parse this by looping over all keys and check them. If one of those checks fail, an error message should be printed and this key value pair should be skipped. This is how my implementation looks at the moment. I hope you doesn't get distracted from some of the engine specific calls.

ModuleSettings *module = (ModuleSettings*)HelperScript::Unwrap(args.Data());

if(args.Length() < 1 || !args[0]->IsObject())
    return v8::Undefined();
v8::Handle<v8::Object> object = args[0]->ToObject();

auto stg = module->Global->Get<Settings>("settings");

v8::Handle<v8::Array> keys = object->GetPropertyNames();
for(unsigned int i = 0; i < keys->Length(); ++i)
{
    string key = *v8::String::Utf8Value(keys->Get(i));
    if(!object->Get(v8::String::New(key.c_str()))->IsArray())
    {
        HelperDebug::Fail("script", "could not parse (" + key + ") setting");
        continue;
    }
    v8::Handle<v8::Array> values = v8::Handle<v8::Array>::Cast(object->Get(v8::String::New(key.c_str())));

    if(!values->Has(0) || !values->Get(0)->IsString())
    {
        HelperDebug::Fail("script", "could not parse (" + key + ") setting");
        continue;
    }
    string type = *v8::String::Utf8Value(values->Get(0));

    if(type == "bool")
    {
        if(!values->Has(1) || !values->Get(1)->IsBoolean())
        {
            HelperDebug::Fail("script", "could not parse (" + key + ") setting");
            continue;
        }
        stg->Set<bool>(key, values->Get(1)->BooleanValue());
    }
    else if(type == "Vector2u")
    {
        if(!values->Has(1) || !values->Has(2) || !values->Get(1)->IsUint32(), !values->Get(2)->IsUint32())
        {
            HelperDebug::Fail("script", "could not parse (" + key + ") setting");
            continue;
        }
        stg->Set<Vector2u>(key, Vector2u(values->Get(1)->Uint32Value(), values->Get(2)->Uint32Value()));
    }
    else if(type == "string")
    {
        if(!values->Has(1) || !values->Get(1)->IsString())
        {
            HelperDebug::Fail("script", "could not parse (" + key + ") setting");
            continue;
        }
        stg->Set<string>(key, *v8::String::Utf8Value(values->Get(1)));
    }
}

As you can see, I defined when happens when a check fails at every filter.

HelperDebug::Fail("script", "could not parse (" + key + ") setting");
continue;

I would like to only write that once, but I can only come up with a way using goto which I would like to prevent. Is there a better option to restructure the if else construct?


Solution

  • I think I'd start with a set of small classes to do the verification step for each type:

    auto v_string = [](v8::Handle<v8::Array> const &v) { 
        return v->Has(1) && v->Get(1)->IsString(); 
    }
    
    auto v_Vector2u = [](v8::Handle<v8::Array> const &v) { 
        return v->Has(1) && v->Has(2) && 
               v->Get(1)->IsUint32() && v->Get(2)->IsUint32(); 
    }
    // ...
    

    Then I'd create a map from the name of a type to the verifier for that type:

    std::map<std::string, decltyp(v_string)> verifiers;
    
    verifiers["string"] = v_string;
    verifiers["Vector2u"] = v_Vector2u;
    // ...
    

    Then to verify a type, you'd use something like this:

    // find the verifier for this type:
    auto verifier = verifiers.find(type);
    
    // If we can't find a verifier, reject the data:
    if (verifier == verifiers.end())
        HelperDebug::Fail("script", "unknown type: " + type);
    
    // found the verifier -- verify the data:
    if (!verifier->second(values))
        HelperDebug::Fail("script", "could not parse (" + key + ") setting");