Skip to content

Commit

Permalink
feat(logic): add util func to extract json object attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
bdeneux committed Apr 28, 2023
1 parent c0b5a6c commit 9b58497
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
46 changes: 46 additions & 0 deletions x/logic/predicate/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,49 @@ func AtomBool(b bool) engine.Term {
}

var AtomNull = engine.NewAtom("@").Apply(engine.NewAtom("null"))

// ExtractJsonTerm is an utility function that would extract all attribute of a JSON object
// that is represented in prolog with the `json` atom.
//
// This function will ensure the json atom follow our json object representation in prolog.
//
// A JSON object is represented like this :
//
// ```
// json([foo-bar])
// ```
//
// That give a JSON object: `{"foo": "bar"}`
// Returns the map of all attributes with its term value.
func ExtractJsonTerm(term engine.Compound, env *engine.Env) (map[string]engine.Term, error) {
if term.Functor() != AtomJSON {
return nil, fmt.Errorf("invalid functor %s. Expected %s", term.Functor().String(), AtomJSON.String())
} else if term.Arity() != 1 {
return nil, fmt.Errorf("invalid compound arity : %d but expected %d", term.Arity(), 1)
}

list := term.Arg(0)
switch l := env.Resolve(list).(type) {
case engine.Compound:
iter := engine.ListIterator{
List: l,
Env: env,
}
terms := make(map[string]engine.Term, 0)
for iter.Next() {
pair, ok := env.Resolve(iter.Current()).(engine.Compound)
if !ok || pair.Functor() != AtomPair || pair.Arity() != 2 {
return nil, fmt.Errorf("json attributes should be a pair")
}

key, ok := env.Resolve(pair.Arg(0)).(engine.Atom)
if !ok {
return nil, fmt.Errorf("first pair arg should be an atom")
}
terms[key.String()] = pair.Arg(1)
}
return terms, nil
default:
return nil, fmt.Errorf("json compound should contains one list, give %T", l)
}
}
80 changes: 80 additions & 0 deletions x/logic/predicate/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package predicate

import (
"fmt"
"testing"

"github.com/ichiban/prolog/engine"
. "github.com/smartystreets/goconvey/convey"
)

func TestExtractJsonTerm(t *testing.T) {
Convey("Given a test cases", t, func() {
cases := []struct {
compound engine.Compound
result map[string]engine.Term
wantSuccess bool
wantError error
}{
{
compound: engine.NewAtom("foo").Apply(engine.NewAtom("bar")).(engine.Compound),
wantSuccess: false,
wantError: fmt.Errorf("invalid functor foo. Expected json"),
},
{
compound: engine.NewAtom("json").Apply(engine.NewAtom("bar"), engine.NewAtom("foobar")).(engine.Compound),
wantSuccess: false,
wantError: fmt.Errorf("invalid compound arity : 2 but expected 1"),
},
{
compound: engine.NewAtom("json").Apply(engine.NewAtom("bar")).(engine.Compound),
wantSuccess: false,
wantError: fmt.Errorf("json compound should contains one list, give engine.Atom"),
},
{
compound: AtomJSON.Apply(engine.List(AtomPair.Apply(engine.NewAtom("foo"), engine.NewAtom("bar")))).(engine.Compound),
result: map[string]engine.Term{
"foo": engine.NewAtom("bar"),
},
wantSuccess: true,
},
{
compound: AtomJSON.Apply(engine.List(engine.NewAtom("foo"), engine.NewAtom("bar"))).(engine.Compound),
wantSuccess: false,
wantError: fmt.Errorf("json attributes should be a pair"),
},
{
compound: AtomJSON.Apply(engine.List(AtomPair.Apply(engine.Integer(10), engine.NewAtom("bar")))).(engine.Compound),
wantSuccess: false,
wantError: fmt.Errorf("first pair arg should be an atom"),
},
}
for nc, tc := range cases {
Convey(fmt.Sprintf("Given the term compound #%d: %s", nc, tc.compound), func() {
Convey("when extract json term", func() {
env := engine.Env{}
result, err := ExtractJsonTerm(tc.compound, &env)

if tc.wantSuccess {
Convey("then no error should be thrown", func() {
So(err, ShouldBeNil)
So(result, ShouldNotBeNil)

Convey("and result should be as expected", func() {
So(result, ShouldResemble, tc.result)
})
})
} else {
Convey("then error should occurs", func() {
So(err, ShouldNotBeNil)

Convey("and should be as expected", func() {
So(err, ShouldResemble, tc.wantError)
})
})
}
})
})
}
})
}

0 comments on commit 9b58497

Please sign in to comment.