func TestDump(t *testing.T) { ctx := context.Background() input := `{"a": [1,2,3,4]}` var data map[string]interface{} err := util.UnmarshalJSON([]byte(input), &data) if err != nil { panic(err) } store := storage.New(storage.InMemoryWithJSONConfig(data)) var buffer bytes.Buffer repl := newRepl(store, &buffer) repl.OneShot(ctx, "dump") expectOutput(t, buffer.String(), "{\"a\":[1,2,3,4]}\n") }
func TestDumpPath(t *testing.T) { ctx := context.Background() input := `{"a": [1,2,3,4]}` var data map[string]interface{} err := util.UnmarshalJSON([]byte(input), &data) if err != nil { panic(err) } store := storage.New(storage.InMemoryWithJSONConfig(data)) var buffer bytes.Buffer repl := newRepl(store, &buffer) dir, err := ioutil.TempDir("", "dump-path-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) file := filepath.Join(dir, "tmpfile") repl.OneShot(ctx, fmt.Sprintf("dump %s", file)) if buffer.String() != "" { t.Errorf("Expected no output but got: %v", buffer.String()) } bs, err := ioutil.ReadFile(file) if err != nil { t.Fatalf("Expected file read to succeed but got: %v", err) } var result map[string]interface{} if err := util.UnmarshalJSON(bs, &result); err != nil { t.Fatalf("Expected json unmarhsal to suceed but got: %v", err) } if !reflect.DeepEqual(data, result) { t.Fatalf("Expected dumped json to equal %v but got: %v", data, result) } }
func executeQuery(data string, compiler *ast.Compiler, tracer topdown.Tracer) { topdown.ResetQueryIDs() d := map[string]interface{}{} if len(data) > 0 { if err := util.UnmarshalJSON([]byte(data), &d); err != nil { panic(err) } } ctx := context.Background() store := storage.New(storage.InMemoryWithJSONConfig(d)) txn := storage.NewTransactionOrDie(ctx, store) defer store.Close(ctx, txn) params := topdown.NewQueryParams(ctx, compiler, store, txn, nil, ast.MustParseRef("data.test.p")) params.Tracer = tracer _, err := topdown.Query(params) if err != nil { panic(err) } }
func newTestStore() *storage.Storage { input := ` { "a": [ { "b": { "c": [true,2,false] } }, { "b": { "c": [false,true,1] } } ] } ` var data map[string]interface{} err := util.UnmarshalJSON([]byte(input), &data) if err != nil { panic(err) } return storage.New(storage.InMemoryWithJSONConfig(data)) }
func ExampleEval() { // Initialize context for the example. Normally the caller would obtain the // context from an input parameter or instantiate their own. ctx := context.Background() compiler := ast.NewCompiler() // Define a dummy query and some data that the query will execute against. query, err := compiler.QueryCompiler().Compile(ast.MustParseBody("data.a[_] = x, x >= 2")) if err != nil { // Handle error. } var data map[string]interface{} // OPA uses Go's standard JSON library but assumes that numbers have been // decoded as json.Number instead of float64. You MUST decode with UseNumber // enabled. decoder := json.NewDecoder(bytes.NewBufferString(`{"a": [1,2,3,4]}`)) decoder.UseNumber() if err := decoder.Decode(&data); err != nil { // Handle error. } // Instantiate the policy engine's storage layer. store := storage.New(storage.InMemoryWithJSONConfig(data)) // Create a new transaction. Transactions allow the policy engine to // evaluate the query over a consistent snapshot fo the storage layer. txn, err := store.NewTransaction(ctx) if err != nil { // Handle error. } defer store.Close(ctx, txn) // Prepare the evaluation parameters. Evaluation executes against the policy // engine's storage. In this case, we seed the storage with a single array // of number. Other parameters such as the request, tracing configuration, // etc. can be set on the Topdown object. t := topdown.New(ctx, query, compiler, store, txn) result := []interface{}{} // Execute the query and provide a callbakc function to accumulate the results. err = topdown.Eval(t, func(t *topdown.Topdown) error { // Each variable in the query will have an associated "binding" in the context. x := t.Binding(ast.Var("x")) // The bindings are ast.Value types so we will convert to a native Go value here. v, err := topdown.ValueToInterface(x, t) if err != nil { return err } result = append(result, v) return nil }) // Inspect the query result. fmt.Println("result:", result) fmt.Println("err:", err) // Output: // result: [2 3 4] // err: <nil> }
func TestPrettyTrace(t *testing.T) { module := ` package test p :- q[x], plus(x, 1, n) q[x] :- x = data.a[_] ` ctx := context.Background() compiler := compileModules([]string{module}) data := loadSmallTestData() store := storage.New(storage.InMemoryWithJSONConfig(data)) txn := storage.NewTransactionOrDie(ctx, store) defer store.Close(ctx, txn) params := NewQueryParams(ctx, compiler, store, txn, nil, ast.MustParseRef("data.test.p")) tracer := NewBufferTracer() params.Tracer = tracer _, err := Query(params) if err != nil { panic(err) } expected := `Enter eq(data.test.p, _) | Eval eq(data.test.p, _) | Enter p = true :- data.test.q[x], plus(x, 1, n) | | Eval data.test.q[x] | | Enter q[x] :- eq(x, data.a[_]) | | | Eval eq(x, data.a[_]) | | | Exit q[x] :- eq(x, data.a[_]) | | Eval plus(x, 1, n) | | Exit p = true :- data.test.q[x], plus(x, 1, n) | Redo p = true :- data.test.q[x], plus(x, 1, n) | | Redo data.test.q[x] | | Redo q[x] :- eq(x, data.a[_]) | | | Redo eq(x, data.a[_]) | | | Exit q[x] :- eq(x, data.a[_]) | | Eval plus(x, 1, n) | | Exit p = true :- data.test.q[x], plus(x, 1, n) | Redo p = true :- data.test.q[x], plus(x, 1, n) | | Redo data.test.q[x] | | Redo q[x] :- eq(x, data.a[_]) | | | Redo eq(x, data.a[_]) | | | Exit q[x] :- eq(x, data.a[_]) | | Eval plus(x, 1, n) | | Exit p = true :- data.test.q[x], plus(x, 1, n) | Redo p = true :- data.test.q[x], plus(x, 1, n) | | Redo data.test.q[x] | | Redo q[x] :- eq(x, data.a[_]) | | | Redo eq(x, data.a[_]) | | | Exit q[x] :- eq(x, data.a[_]) | | Eval plus(x, 1, n) | | Exit p = true :- data.test.q[x], plus(x, 1, n) | Exit eq(data.test.p, _) ` a := strings.Split(expected, "\n") var buf bytes.Buffer PrettyTrace(&buf, *tracer) b := strings.Split(buf.String(), "\n") min := len(a) if min > len(b) { min = len(b) } for i := 0; i < min; i++ { if a[i] != b[i] { t.Errorf("Line %v in trace is incorrect. Expected %v but got: %v", i+1, a[i], b[i]) } } if len(a) < len(b) { t.Fatalf("Extra lines in trace:\n%v", strings.Join(b[min:], "\n")) } else if len(b) < len(a) { t.Fatalf("Missing lines in trace:\n%v", strings.Join(a[min:], "\n")) } }
func ExampleStorage_Write() { // Initialize context for the example. Normally the caller would obtain the // context from an input parameter or instantiate their own. ctx := context.Background() // Define some dummy data to initialize the DataStore with. exampleInput := ` { "users": [ { "name": "alice", "color": "red", "likes": ["clouds", "ships"] }, { "name": "burt", "likes": ["cheese", "wine"] } ] } ` var data map[string]interface{} // OPA uses Go's standard JSON library but assumes that numbers have been // decoded as json.Number instead of float64. You MUST decode with UseNumber // enabled. decoder := json.NewDecoder(bytes.NewBufferString(exampleInput)) decoder.UseNumber() if err := decoder.Decode(&data); err != nil { // Handle error. } // Create the new DataStore with the dummy data. store := storage.New(storage.InMemoryWithJSONConfig(data)) // Define dummy data to add to the DataStore. examplePatch := `{ "longitude": 82.501389, "latitude": -62.338889 }` var patch interface{} // See comment above regarding decoder usage. decoder = json.NewDecoder(bytes.NewBufferString(examplePatch)) decoder.UseNumber() if err := decoder.Decode(&patch); err != nil { // Handle error. } txn, err := store.NewTransaction(ctx) if err != nil { // Handle error. } defer store.Close(ctx, txn) // Write values into storage and read result. err0 := store.Write(ctx, txn, storage.AddOp, storage.MustParsePath("/users/0/location"), patch) v1, err1 := store.Read(ctx, txn, storage.MustParsePath("/users/0/location/latitude")) err2 := store.Write(ctx, txn, storage.ReplaceOp, storage.MustParsePath("/users/1/color"), "red") // Inspect the return values. fmt.Println("err0:", err0) fmt.Println("v1:", v1) fmt.Println("err1:", err1) fmt.Println("err2:", err2) // Rollback transaction because write failed. // Output: // err0: <nil> // v1: -62.338889 // err1: <nil> // err2: storage error (code: 1): bad path: /users/1/color, document does not exist }
func ExampleStorage_Read() { // Initialize context for the example. Normally the caller would obtain the // context from an input parameter or instantiate their own. ctx := context.Background() // Define some dummy data to initialize the built-in store with. exampleInput := ` { "users": [ { "name": "alice", "color": "red", "likes": ["clouds", "ships"] }, { "name": "burt", "likes": ["cheese", "wine"] } ] } ` var data map[string]interface{} // OPA uses Go's standard JSON library but assumes that numbers have been // decoded as json.Number instead of float64. You MUST decode with UseNumber // enabled. decoder := json.NewDecoder(bytes.NewBufferString(exampleInput)) decoder.UseNumber() if err := decoder.Decode(&data); err != nil { // Handle error. } // Instantiate the storage layer. store := storage.New(storage.InMemoryWithJSONConfig(data)) txn, err := store.NewTransaction(ctx) if err != nil { // Handle error. } defer store.Close(ctx, txn) // Read values out of storage. v1, err1 := store.Read(ctx, txn, storage.MustParsePath("/users/1/likes/1")) v2, err2 := store.Read(ctx, txn, storage.MustParsePath("/users/0/age")) // Inspect the return values. fmt.Println("v1:", v1) fmt.Println("err1:", err1) fmt.Println("v2:", v2) fmt.Println("err2:", err2) fmt.Println("err2 is not found:", storage.IsNotFound(err2)) // Output: // v1: wine // err1: <nil> // v2: <nil> // err2: storage error (code: 1): bad path: /users/0/age, document does not exist // err2 is not found: true }