Open toniphan21 opened 5 months ago
Minder analyzed this PR and found it does not add any new vulnerable dependencies.
Vulnerability scan of
9f28e15e:
- 🐞 vulnerable packages:
0
- 🛠 fixes available for:
0
Hey @toniphan21, thanks for this PR and the well thought out description to bring us up to speed! ❤️
Just to set some expectations, it might take us a little while to get around to fully reviewing this PR. We'd like to make sure we fully understand the pieces that are new to us (pkl, generation of files outside the model) and how the full model development cycle looks with this change (e.g. local and CI usage, potentially expanding to how this may fit into areas like the playground).
We're definitely excited to see the suggestions you have for improving the testing of a model. Out of curiosity is this something you're using to author tests currently?
Hey @ewanharris, thank you for reviewing this PR. I understand it's a big one, so I don't expect the review process to be fast. Also, you can see I haven't added any tests yet. The reason is that I want to gather your feedback before adding them.
To answer your question, yes, I do use these tests at work to verify the model with non-technical stakeholders. That's why readability is the main focus.
Let me walk you through the process of how I came up with the idea, and then I'll suggest some approaches for integrating pkl into openfga's ecosystem.
First, I started with the yaml file for testing a model:
name: test
model: ''
tuples:
- user: user:1
relation: member
object: org:1
tests:
- name: whatever
check:
- user: user:1
object: object
assertions:
allow: true
Then I wrote a pkl
file which could be converted to the exact yaml file above:
name: String = "test"
model: String = ""
tuples: Listing<Tuple> = new {
new {
user = "user:1"
relation = "member"
object = "org:1"
}
}
tests: Listing<TestCase> = new {
new {
name = "whatever"
check {
new {
user = "user:1"
object = "object"
assertions {
["allow"] = true
}
}
}
}
}
class Test {
name: String
model: String?
tuples: Listing<Tuple>
tests: Listing<TestCase>
}
class Tuple {
user: String
relation: String
object: String
}
class TestCase {
name: String?
check: Listing<Check>
}
class Check {
user: String
object: String
assertions: Mapping<String, Boolean>
}
As you can see, the raw pkl file is longer than the yaml file. However, there are some upsides to using pkl:
new {
user = "user:1"
relation = "member"
object = "org:1"
}
becomes
// somewhere at the top of the pkl file
local Anna = new User { id = "1" }
local OpenFGA = new Org { id = "1" }
Anna.member(OpenFGA) // member is a function of class User which returns the structure above
The benefit is that I can name member
whatever I want and still produce the same output. I can condense every setup (producing a tuple) into one line. Another advantage is that in the function, we can produce more than one tuple (for example, a user belongs to an org - one tuple, and the org has the member - another tuple).
In short, the pkl
file is just a nicer way to express ideas or business logic; in the end, it will be converted to yaml and tested using the openfga cli.
However, I faced a problem where I have to write a pkl
class every time my model changes. That’s why I want to generate pkl
files automatically based on the model, saving time and avoiding mistakes.
I suggest that:
I think this is a lot of work, and it’s not easy to integrate a new language into an ecosystem. However, I believe pkl offers a lot compared to yaml, and generating source code from a model is a good start. Please test this PR with any models in the sample store and give me feedback. I hope the idea is clearer after you've tried it.
Introduce new command
generate
to generate some code for testing using pkl-lang.Motivation
I want to have a testing system for an Authorization Model that:
Solution
Using pkl-lang to write tests instead of yaml. The process of testing is:
pkl
lang, which is type safe and have IDE integrationsyaml
file and usefga model --tests
command to perform testHowever, written tests in
pkl
lang is not the completed solution, we need something to decouple the business logic and authorization model. It means if the authorization model changed, the changes we make inpkl
files should be minimum (it's not possible withyaml
because all tuples are tired to the authorization model structure). So the final architecture looks like this:Usage example:
Employee
,Company
, and they have some rules need to be tested.fga generate pkl
to generate code based on that model.fga model --tests
, also they are easy to understand and readable for non-technical people.Company
now will be renamed toOrganization
technically and introduce a tree-like structure. So all old business logics are still valid, but the model and tuples aren't. What we need to do is:fga generate pkl
to regenerate codeBy doing this way there are some advantages:
yaml
fileType
in the Adapter layerChanges description
Introduce a new command
generate
has a subcommandpkl
which generate a smallpkl
code library with assertions and types from given openFGA model. The purpose of this command is provide functionality for Generate Pkl layer. The structure of generated directory looks like:testing/lib/test.pkl
: small library file to match the syntax of pkl to yaml file for testing a model - this file always be overridden when run the commandtesting/lib/gen.pkl
: generated types and assertions based on model - this file always be overridden when run the commandtesting/type.pkl
: a types definitions where we can write some functions to make the tests setup looks nicer - only generated if the file is not found.testing/assertions.pkl
: assertions library where we can write some functions to make the tests assertions looks nicer - only generated if the file is not found.testing/test_example.pkl
: an example test - only generated if the file is not found.testing/run
: a simple bash script helper to run a test or all tests - only generated if the file is not found.More about pkl-lang: https://pkl-lang.org/
Example
Run this command with the model
custom-roles
in sample-store.The generated contents are:
testing/run
testing/type.pkl
testing/assertions.pkl
testing/lib/...
These files are too big, but it looks like this
Write Test after running the command
the
type.pkl
is where we put some setup helper functions, such as:In the code above, we use
relation_member
generated from the model to express thatan org can has_user()
with type safety. Then in the class User:We express that we want to test a user in a specific org, and use generated assert helper handle the check.
Then we can start writing test for multiple-scenarios, each scenarios has it own setup and file, looks like
test_example
:as you see, in the setup we could use generated function or custom function (which is nicer), and in the tests we could test user Anna in a specific Organization with very nice syntax.
Now, to run the test use
run
utility, or by using raw command:That's all.
Review Checklist
main