FirebaseExtended / protobuf-rules-gen

This is an experimental protoc plugin that generates Firebase Rules for Cloud Firestore based on Google's Protocol Buffer format. This allows you to easily validate your data in a platform independent manner.
Apache License 2.0
197 stars 13 forks source link

Best way to split messages into multiple .proto files? #33

Open adamduren opened 5 years ago

adamduren commented 5 years ago

What is the recommended way of splitting messages across multiple proto files?

I tried sending multiple files to protoc but I get the error firestore.rules: Tried to write the same file twice. The next thought was to have a base.proto which imports the other .proto files but in trying this I am not sure how to export the imports from the base.proto so the output is empty.

My last thought is a shell script to execute protoc multiple times and then to cat the result together.

Any advice / insight would be greatly appreciated!

rockwotj commented 5 years ago

I think this is going to need some code changes, ideally we let you give us multiple proto files: However we hard code a single file at the moment if you're not using bazel rules. Maybe we should remove that if statement and always name the files uniquely. https://github.com/FirebaseExtended/protobuf-rules-gen/blob/master/firebase_rules_generator/generator.cc#L225

Thoughts? Interested in opening a PR for this? The tests will need to be updated too.

Alternatively, if you want to learn Bazel, someone contributed bazel rules that lets you specify multiple proto files, then concats them together for you, see our example: https://github.com/FirebaseExtended/protobuf-rules-gen/tree/master/example (firestore_rules_binary can take multiple firestore_rules_proto_library as deps)

nilsreichardt commented 3 years ago

I wrote a little script, which generate me my firestore.rules from multiple .proto files. It also keeps the security rules current rules in firestore.rules.

This is my folder structure:

cloud_firestore
   src
     protobuf
       - person.proto
       - user.proto
     rules
       - firestore.rules
     scripts
       - generate_protobuf_methods.js
     test
       - (my unit tests)

This is my firestore.rules before running the script:

// Start your rules (these don't get generated!)
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth.uid == userId;
      allow write: if isPersonMessage(request.resource.data) &&
                      request.auth.uid == userId;
    }
  }
}

This is the result after running my script (node src/scripts/generate_protobuf_methods.js):

// @@START_GENERATED_FUNCTIONS@@
function isPersonMessage(resource) {
  return resource.keys().hasAll(['name']) &&
          (resource.keys().hasOnly(['name','phone','email'])) &&
          ((resource.name is string)) &&
          ((!resource.keys().hasAny(['email'])) || (resource.email is string)) &&
          ((!resource.keys().hasAny(['phone'])) || (isPerson_PhoneNumberMessage(resource.phone)));
}
function isPerson_PhoneNumberMessage(resource) {
  return resource.keys().hasAll([]) &&
          (resource.keys().hasOnly(['type','number'])) &&
          ((!resource.keys().hasAny(['number'])) || (resource.number is string)) &&
          ((!resource.keys().hasAny(['type'])) || (isPerson_PhoneTypeEnum(resource.type)));
}
function isPerson_PhoneTypeEnum(resource) {
  return resource == 0 ||
          resource == 1 ||
          resource == 2;
}
function isUserMessage(resource) {
  return resource.keys().hasAll([]) &&
          (resource.keys().hasOnly(['phone','email'])) &&
          ((!resource.keys().hasAny(['email'])) || (resource.email is string)) &&
          ((!resource.keys().hasAny(['phone'])) || (isUser_UserNumberMessage(resource.phone)));
}
function isUser_UserNumberMessage(resource) {
  return resource.keys().hasAll([]) &&
          (resource.keys().hasOnly(['type','number'])) &&
          ((!resource.keys().hasAny(['number'])) || (resource.number is string)) &&
          ((!resource.keys().hasAny(['type'])) || (isUser_UserTypeEnum(resource.type)));
}
function isUser_UserTypeEnum(resource) {
  return resource == 0 ||
          resource == 1 ||
          resource == 2;
}
// @@END_GENERATED_FUNCTIONS@@

// Start your rules (these don't get generated!)
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth.uid == userId;
      allow write: if isPersonMessage(request.resource.data) &&
                      request.auth.uid == userId;
    }
  }
}

You will find the script under this link: https://gist.github.com/nilsreichardt/9f72898f8bf2aa655d0ef2cce74ac2cf