GSoC Week1
Date: June 13, 2022 → June 19, 2022
Overview This week’s tasks
- Review https://github.com/casbin/casbin-rs/pull/293
- Make 2 issue & 2 PRs
- Explore swagger code generator and explain why it doesn’t work
Task 1 Review PR
https://github.com/casbin/casbin-rs/pull/293
Original implementation
Original implementation use Arc<RwLock<Role>> :
1 | pub struct DefaultRoleManager { |
The key of HashMap<String, HashMap<String, Arc<RwLock<Role>>>> is domain name (default value is DEFAULT), the key of HashMap<String, Arc<RwLock<Role>>> is role name, the entry is Arc<RwLock<Role>> , which stores role information: name of the role, and all roles it directly has
It is quite expensive as Arc<T> uses atomic ****operations for its reference counting, and there are many read() and write() operations for RwLock<Role> in current implmentation:
1 | //L185 |
Important functions:
add_linkappendrole2torole1.rolesdelete_linkdeleterole2fromrole1.roleshas_linkcheck if rolename1has rolename2(dosen’t have to be direct role), ifdomain_matching_fnis speicified, it will find roles in matched domainsget_rolesget all direct roles usernamehas in matched domainsget_usersget all users that directly have rolenamein matched domains
Novel implementation
This PR introduces 2 new crates:
- https://github.com/petgraph/petgraph graph data structure library, use
StableDiGraphto store roles - https://github.com/petgraph/fixedbitset a simple bitset container, used in BFS to mark visited nodes
This PR’s implementation is like Go Casbin (https://github.com/casbin/casbin/blob/master/rbac/default-role-manager/role_manager.go)
1 | type Role struct { |
But instead of using map and storing related roles’ pointers in them, it use StableDiGraph to construct roles graph, and use edges EdgeVariant to represent relations:
1 | enum EdgeVariant { |
Edges with the EdgeVariant::Link are relations which in Go are modeled by the roles and users map.
Edges with the EdgeVariant::Match are relations which is Go are modeled by the matched and matchedBy map.
This PR gets rid of Arc<RwLock<Role>> by using StableDiGraph and NodeIndex :
1 | pub struct DefaultRoleManager { |
StableGraph<N, E, Ty, Ix> is a graph datastructure using an adjacency list representation, all_domains constructs a StableGraph with directed edges for each domain, all_domains_indices stores node identifier NodeIndex of every role node in the graph
Important functions:
get_or_create_roleget or create role;
if the role is new,
graph.add_node()to add role to grapgh;if
role_matching_fnis specified, calllink_if_matchesto match existing roles against new role and vice versa, if matched, callgraph.add_edge()to createEdgeVariant::*Match*edge between roles.add_linkadd link from
role1torole2, callgraph.add_edge()to addEdgeVariant::*Linkedge*delete_linkremove edge from
role1torole2has_linkBfs searching in graph, checking if
role1is connected torole2get_rolesBfs searching in graph, getting all roles the user directly has
get_usersFind all nodes having
Direction::*Incoming* edge connected to this role, that is, getting all users that directly have this role
Why Performance impoved?
In StableGraph, nodes (roles) and edges (relations) are each numbered in an interval from 0 to some number m, so we can access nodes and edges using their indices, also, creating a role is just adding a node to graph, link roles is justing add an edge between 2 nodes, we don’t have to modify nodes and edges, so Arc and RWLock are no longer needed.
Without atomic operations and lock/unlock, role manager is much faster now.
Tests
The PR add 2 tests:
test_basic_role_matchingtest user with wildcard *test_basic_role_matching2test role with wildcard *
Migrate these tests to Go Casbin:
1 | func TestBasicRoleMatching(t *testing.T) { |
TestBasicRoleMatching passed, TestBasicRoleMatching2 failed with:
1 | role_manager_test.go:371: alice: [* book_group alice book_group bob pen_group], supposed to be [* alice bob book_group pen_group] |
Note that book_group appears twice, this is because in (*Role).rangeRoles :
1 | func (r *Role) rangeRoles(fn func(key, value interface{}) bool) { |
All roles, matched, and matchedBy roles are appended to result, which is a list, whille casbin-rs uses HashSet , so there are no duplicate in casbin-rs.
Benchmark
Benchmark changes from GitHub workflow:
https://github.com/casbin/casbin-rs/runs/6409294114
1 | group changes master |
Improved above 5%:
- b_benchmark_rbac_model_large -21%
- benchmark_rbac_model_medium -11%
- benchmark_role_manager_medium -24%
- benchmark_role_manager_small -8.3%
Regressed above 2%:
- benchmark_role_manager_large +4.0%
And I found that benchmark workflow doesn’t work well:
https://github.com/casbin/casbin-rs/runs/6409294114#step:5:459

It fails to post comment of benchmark result to current PR with status code 403
Task 2 Make 2 issue & 2 PRs
issue: benchmark workflow is unable to push benchmark result to PR comment
https://github.com/casbin/casbin-rs/issues/294
issue: As I said above, when reviewing PR, I found a bug in Go version casbin. In default-role-manager/role_manager.go, func (r *Role) getRoles() returns result with duplicate items
https://github.com/casbin/casbin/issues/1033
PR: there are 3 errors when running cargo clippy -- -D warnings , I fixed it
https://github.com/casbin/casbin-rs/pull/296
PR: There are many duplicate code in default_role_manager, making it hard to understand and maintain code, I improved it.
https://github.com/casbin/casbin-rs/pull/295
Task 3 Swagger code generator
There are 105 enpoints in casdoor now, so when developing casdoor rust sdk, I have to write many similar and duplicate code. To avoid this, I first looked for code generator.
As Casdoor provide well annotated swagger spec (https://github.com/casdoor/casdoor/blob/master/swagger/swagger.yml), I can generate code using swagger code generator.
First, I found swagger-codegen.
https://github.com/swagger-api/swagger-codegen
It can generate rust code with 2 different implementation.
I use docker image to generate code.
I tried first implementation:
1 | docker run -u 1000:1000 --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate \ |
Code is generated very fast. The output is like:

However, when running cargo build , I got many warnings like:

After searching a while on the web, I found work around (https://stackoverflow.com/a/57641467): add #![allow(warnings)] at the first line in src/lib.rs
Now cargo build doesn’t produce warnings now.

However, as the warnings are still here, this doesn’t help much, there are still many deprecated code and badly named variables.
So I tried second implementation:
1 | docker run -u 1000:1000 --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate \ |
Now cargo build failes with:

Searching issues of rust-openssl for a while, I found similar issue with me:
https://github.com/sfackler/rust-openssl/issues/1436
It says rust-openssl v0.9.24 is too old and doesn’t support OpenSSL 1.1.1
My openssl version is 1.1.1

Obviously, swagger code generator is not maintained for a while, and we should find another code generator.
Then I found openapi-generator:
https://github.com/OpenAPITools/openapi-generator
The usage is similar to swagger-codegen:
1 | docker run -u 1000:1000 --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate |
Code generator fails with:

It says that because some endpoints in swagger.yml aren’t providing response, so the spec validation failes. For example, GetResources endpoint is not well annotated (no params, no response)

Then I found paperclip:
https://github.com/paperclip-rs/paperclip
It’s a WIP OpenAPI tooling written in Rust and can also generate rust code.
1 | paperclip --api v2 -o out/paper swagger.yml |
Still, as there are several endpoints not well annotated, it fails with error:

Also, generated code can’t have similar API with Go version Casdoor SDK:
https://github.com/casdoor/casdoor-go-sdk
After I explored these two code generators, I gave up and decide to write code on my own.
Summary:
- generated code is difficult to maintain
- generated code is too old and use deprecated syntax
- generated code is badly written and has many warnings
- generated code can’t have similar API with Go version Casdoor SDK
- swagger.yml is not complete (some endpoint without response), some code generator will report syntax error
Next week Plan
Next week, I will:
- start writing code for casdoor-rust-sdk
- learn how middleware works and contribute to poem-casbin
- maintain projects of casbin-rs
Wakatime weekly stats
