goprologffiiso-prolog

How do I pass parameters to ichiban/prolog's Query function?


I have the following Go code:

package main

import (
    "context"
    _ "embed"
    "fmt"
    "strings"
    "time"

    "github.com/ichiban/prolog"
)

//go:embed permissions.pl
var permissions string

type User struct {
    Name  string
    Roles []string
}

func (u *User) CheckPerm(ctx context.Context, model, op string) bool {
    userRules := make([]string, 0, len(u.Roles))

    for _, role := range u.Roles {
        userRules = append(userRules, fmt.Sprintf("hasRole(user, %s).", role))
    }

    rules := strings.Join(userRules, "\n")

    p := prolog.New(nil, nil)

    err := p.ExecContext(ctx, permissions)
    if err != nil {
        panic(err)
    }

    err = p.ExecContext(ctx, rules)
    if err != nil {
        panic(err)
    }

    // result, err := p.QueryContext(ctx, fmt.Sprintf("checkPerm(user, %s, %s).", model, op))
    result, err := p.QueryContext(ctx, "checkPerm(user, ?, ?).", model, op)
    if err != nil {
        panic(err)
    }

    return result.Next()
}

func main() {
    start := time.Now()
    defer func() {
        fmt.Printf("DONE: %s\n", time.Since(start))
    }()
    user := &User{
        Name:  "Alice",
        Roles: []string{"jobadmin"},
    }

    ctx := context.Background()

    fmt.Println(user.CheckPerm(ctx, "jobs", "write"))
}

Which references the following permissions.pl:

:- dynamic(hasRole/2).

% role(RoleName, Model, Operation)
role(jobuser, jobs, pivot).
role(jobadmin, jobs, write).
role(jobadmin, jobs, delete).

% if the role can write, the role can read
role(Role, Model, read) :-
    role(Role, Model, write).

role(Role, Model, Operation) :-
    inherit(ParentRole, Role),
    role(ParentRole, Model, Operation).

inherit(jobuser, jobadmin).

checkPerm(User, Model, Operation) :-
    hasRole(User, Role),
    role(Role, Model, Operation).

This is all done for the sake of learning. My goal is to specify RBAC rules in permissions.pl, append a few rules that define the user's roles, then query with checkPerm/3 to see true or false if the user has permission.

If I load up permissions.pl in swipl and run assert(hasRole(user, jobadmin)), checkPerm(user, jobs, write). I get true as expected based on the rules. However, when I run the Go code which essentially asks the same question, I get back false. It seems to hinge on how I provide parameters to QueryContext.

In my code sample, I've commented out a version that uses fmt.Sprintf(...) and that approach works correctly (running the Go code produces true). However, QueryContext supports parameters and I'd like to use them properly. I've tried a few approaches to get ? parameters to work: wrap model and op in single quotes or use engine.NewAtom(...) on model and op. But I always get back false without any errors. What am I doing wrong?


Solution

  • Your code didn't work because QueryContext(ctx, "checkPerm(user, ?, ?).", "jobs", "write") is same as QueryContext(ctx, "checkPerm(user, \"jobs\", \"write\")."). Both "jobs" and "write" are double quoted in Prolog which means they're strings (lists of single-character atoms)- [j, o, b, s] and [w, r, i, t, e].

    The preferred solution is to use atom_chars/2 which converts atoms into strings (chars) and vice versa:

    result, err := p.QueryContext(ctx, "atom_chars(Model, ?), atom_chars(Op, ?), checkPerm(user, Model, Op).", model, op)
    

    https://go.dev/play/p/-rx0-72qhOl

    I don't recommend :- set_prolog_flag(double_quotes, atom). because it entirely changes how your prolog processor works just to pass certain parameters.