diff --git a/examples/proto-copier/README.md b/examples/proto-copier/README.md new file mode 100644 index 0000000..1cc5d46 --- /dev/null +++ b/examples/proto-copier/README.md @@ -0,0 +1,293 @@ +# Proto Copier Example + +This example demonstrates the **Proto Copier** functionality in Bufrnix, which allows you to declaratively copy Protocol Buffer files to multiple destinations without code generation. This is useful for: + +- **Distributing proto files** to different services or teams +- **Creating client SDKs** with proto definitions +- **Synchronizing proto files** across microservices +- **Building proto file packages** for different environments +- **Organizing proto files** by access level or team + +## ๐Ÿ—๏ธ Project Structure + +``` +proto-copier/ +โ”œโ”€โ”€ proto/ # Source proto files +โ”‚ โ”œโ”€โ”€ api/v1/ # Public API definitions +โ”‚ โ”‚ โ””โ”€โ”€ user_service.proto # User service API +โ”‚ โ”œโ”€โ”€ common/v1/ # Shared type definitions +โ”‚ โ”‚ โ””โ”€โ”€ types.proto # Common enums and messages +โ”‚ โ”œโ”€โ”€ external/v1/ # External integration APIs +โ”‚ โ”‚ โ””โ”€โ”€ webhook.proto # Webhook service definitions +โ”‚ โ”œโ”€โ”€ internal/v1/ # Internal-only APIs +โ”‚ โ”‚ โ””โ”€โ”€ admin_service.proto # Admin service (excluded from public copies) +โ”‚ โ””โ”€โ”€ test/ # Test proto files +โ”‚ โ””โ”€โ”€ test_user.proto # Test utilities (excluded from production) +โ”œโ”€โ”€ flake.nix # Multiple example configurations +โ””โ”€โ”€ README.md # This file +``` + +## ๐Ÿ“ฆ Available Examples + +### 1. **Default** - Basic Proto Copying +```bash +nix run +``` + +**Configuration:** +- **Preserve structure**: โœ… Maintains directory hierarchy +- **Multiple destinations**: `backend/proto` and `frontend/src/proto` +- **Filters**: Excludes `test/**` and `internal/**` directories +- **Files**: All `.proto` files in the source directory + +### 2. **Advanced Copy** - Multiple Destinations with Fine-Grained Control +```bash +nix run .#advanced-copy +``` + +**Configuration:** +- **Three destinations**: `public-api`, `shared-types`, `mobile-client` +- **Specific patterns**: Only API, common, and external proto files +- **Debug mode**: Enabled with verbose output +- **Smart filtering**: Excludes internal and test files + +### 3. **Flattened Copy** - Single Directory with Transformations +```bash +nix run .#flattened-copy +``` + +**Configuration:** +- **Flattened structure**: All files copied to single directory +- **File transformations**: Adds `proto_` prefix and `_v1` suffix +- **Example**: `user_service.proto` โ†’ `proto_user_service_v1.proto` + +### 4. **API-Only Copy** - Selective File Copying +```bash +nix run .#api-only-copy +``` + +**Configuration:** +- **Specific files**: Only `user_service.proto` and `types.proto` +- **Multiple destinations**: `api-client` and `sdk-generator` +- **Use case**: Creating minimal client libraries + +### 5. **Proto + Go** - Multi-Language Generation +```bash +nix run .#proto-and-go +``` + +**Configuration:** +- **Proto copying**: Distributes proto files to multiple locations +- **Go code generation**: Generates gRPC Go code simultaneously +- **Demonstrates**: How proto copying works alongside other language modules + +## ๐Ÿš€ Quick Start + +1. **Enter development environment:** + ```bash + nix develop + ``` + +2. **Run basic example:** + ```bash + nix run + ``` + +3. **View copied files:** + ```bash + tree output/ + ``` + +4. **Try different examples:** + ```bash + nix run .#advanced-copy + nix run .#flattened-copy + nix run .#api-only-copy + nix run .#proto-and-go + ``` + +## โš™๏ธ Configuration Reference + +### Proto Copier Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `enable` | `bool` | `false` | Enable proto file copying | +| `outputPath` | `string \| string[]` | `["proto/copy"]` | Output destination(s) | +| `preserveStructure` | `bool` | `true` | Maintain directory hierarchy | +| `flattenFiles` | `bool` | `false` | Copy all files to output root | +| `includePatterns` | `string[]` | `["*.proto"]` | File patterns to include | +| `excludePatterns` | `string[]` | `[]` | File patterns to exclude | +| `filePrefix` | `string` | `""` | Prefix for copied file names | +| `fileSuffix` | `string` | `""` | Suffix for copied file names | + +### Example Configuration + +```nix +languages.proto = { + enable = true; + copier = { + enable = true; + outputPath = [ + "backend/proto" + "frontend/src/proto" + "mobile/proto" + ]; + preserveStructure = true; + includePatterns = [ + "api/**/*.proto" + "common/**/*.proto" + ]; + excludePatterns = [ + "**/internal/**" + "**/test/**" + "**/*_test.proto" + ]; + filePrefix = ""; + fileSuffix = "_copy"; + }; +}; +``` + +## ๐Ÿ“ Output Examples + +### Preserved Structure (Default) +``` +output/backend/proto/ +โ”œโ”€โ”€ api/v1/ +โ”‚ โ””โ”€โ”€ user_service.proto +โ”œโ”€โ”€ common/v1/ +โ”‚ โ””โ”€โ”€ types.proto +โ””โ”€โ”€ external/v1/ + โ””โ”€โ”€ webhook.proto +``` + +### Flattened Structure +``` +output/flattened/ +โ”œโ”€โ”€ proto_user_service_v1.proto +โ”œโ”€โ”€ proto_types_v1.proto +โ””โ”€โ”€ proto_webhook_v1.proto +``` + +## ๐ŸŽฏ Use Cases + +### 1. **Microservice Proto Distribution** +Copy public API definitions to multiple services while excluding internal APIs. + +### 2. **Client SDK Generation** +Distribute proto files to client teams for SDK generation in different languages. + +### 3. **Mobile Development** +Copy mobile-relevant proto files to mobile development environments. + +### 4. **Third-Party Integration** +Provide external partners with webhook and integration proto definitions. + +### 5. **Multi-Environment Deployment** +Copy proto files with different filtering rules for development, staging, and production. + +## ๐Ÿ”ง Advanced Features + +### Pattern Matching +- **Glob patterns**: Use `**` for recursive matching +- **Negation**: Exclude patterns override include patterns +- **Flexible**: Support standard shell glob syntax + +### File Transformations +- **Prefixes**: Add consistent prefixes to all copied files +- **Suffixes**: Add version or environment suffixes +- **Combine**: Use both prefixes and suffixes together + +### Multiple Outputs +- **Different rules**: Each output can have different filtering +- **Parallel copying**: All destinations copied in single operation +- **Independent**: Each destination maintains separate directory structure + +## ๐Ÿงช Testing + +### Verify Basic Functionality +```bash +# Run basic copy +nix run + +# Check output structure +tree output/backend/proto +tree output/frontend/src/proto + +# Verify file content +cat output/backend/proto/api/v1/user_service.proto +``` + +### Test Advanced Features +```bash +# Test flattened copy with transformations +nix run .#flattened-copy +ls output/flattened/ + +# Test selective copying +nix run .#api-only-copy +find output/api-client -name "*.proto" +``` + +### Debug Mode +```bash +# Enable debug output +nix run .#advanced-copy +# Check for debug messages in output +``` + +## ๐Ÿค Integration with Other Languages + +The proto copier works seamlessly with other Bufrnix language modules: + +```nix +languages = { + # Copy proto files for distribution + proto.copier = { + enable = true; + outputPath = ["clients/proto"]; + }; + + # Generate Go code + go = { + enable = true; + outputPath = "gen/go"; + grpc.enable = true; + }; + + # Generate TypeScript code + js = { + enable = true; + outputPath = "gen/js"; + es.enable = true; + }; +}; +``` + +## ๐Ÿ” Troubleshooting + +### Common Issues + +1. **No files copied**: Check `includePatterns` and `excludePatterns` +2. **Wrong directory structure**: Verify `preserveStructure` setting +3. **File not found**: Ensure source files exist in `sourceDirectories` +4. **Permission errors**: Check output directory permissions + +### Debug Tips + +1. **Enable debug mode**: Set `debug.enable = true` +2. **Use verbose output**: Set `debug.verbosity = 3` +3. **Check patterns**: Test glob patterns with standard shell tools +4. **Verify paths**: Ensure all paths are relative to project root + +--- + +## ๐Ÿ“š Further Reading + +- [Bufrnix Documentation](https://conneroisu.github.io/bufrnix/) +- [Language Modules Reference](https://conneroisu.github.io/bufrnix/reference/languages/) +- [Configuration Guide](https://conneroisu.github.io/bufrnix/reference/configuration/) +- [More Examples](https://github.com/conneroisu/bufrnix/tree/main/examples) + +This example demonstrates the power and flexibility of Bufrnix's proto copier functionality. Use it as a starting point for your own proto file distribution needs! \ No newline at end of file diff --git a/examples/proto-copier/flake.lock b/examples/proto-copier/flake.lock new file mode 100644 index 0000000..0cb4ed7 --- /dev/null +++ b/examples/proto-copier/flake.lock @@ -0,0 +1,113 @@ +{ + "nodes": { + "bufrnix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "path": "../..", + "type": "path" + }, + "original": { + "path": "../..", + "type": "path" + }, + "parent": [] + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1747958103, + "narHash": "sha256-qmmFCrfBwSHoWw7cVK4Aj+fns+c54EBP8cGqp/yK410=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "fe51d34885f7b5e3e7b59572796e1bcb427eccb1", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1755615617, + "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "20075955deac2583bb12f07151c2df830ef346b4", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "bufrnix": "bufrnix", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1749194973, + "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/examples/proto-copier/flake.nix b/examples/proto-copier/flake.nix new file mode 100644 index 0000000..bf27696 --- /dev/null +++ b/examples/proto-copier/flake.nix @@ -0,0 +1,228 @@ +{ + description = "Proto Copier Example - Demonstrating proto file copying with various configurations"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + # bufrnix.url = "github:conneroisu/bufrnix"; + bufrnix.url = "path:../.."; + bufrnix.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + bufrnix, + }: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages = { + # Basic proto copying - preserve structure, copy all proto files + default = bufrnix.lib.mkBufrnixPackage { + inherit pkgs; + config = { + root = ./.; + protoc = { + sourceDirectories = ["./proto"]; + includeDirectories = ["./proto"]; + files = [ + "./proto/api/v1/user_service.proto" + "./proto/common/v1/types.proto" + "./proto/external/v1/webhook.proto" + "./proto/internal/v1/admin_service.proto" + "./proto/test/test_user.proto" + ]; + }; + languages.proto = { + enable = true; + copier = { + enable = true; + outputPath = [ + "output/backend/proto" + "output/frontend/src/proto" + ]; + preserveStructure = true; + includePatterns = ["*.proto"]; + excludePatterns = [ + "**/test/**" + "**/internal/**" + ]; + }; + }; + }; + }; + + # Advanced example: Multiple output destinations with different configurations + advanced-copy = bufrnix.lib.mkBufrnixPackage { + inherit pkgs; + config = { + root = ./.; + debug = { + enable = true; + verbosity = 2; + }; + protoc = { + sourceDirectories = ["./proto"]; + includeDirectories = ["./proto"]; + }; + languages.proto = { + enable = true; + copier = { + enable = true; + outputPath = [ + "output/public-api" # For external-facing APIs + "output/shared-types" # For shared type definitions + "output/mobile-client" # For mobile app development + ]; + preserveStructure = true; + includePatterns = [ + "api/v1/*.proto" + "common/v1/*.proto" + "external/v1/*.proto" + ]; + excludePatterns = [ + "**/internal/**" + "**/test/**" + "**/*_internal.proto" + ]; + filePrefix = ""; + fileSuffix = ""; + }; + }; + }; + }; + + # Flattened copy example: Copy all files to single directory + flattened-copy = bufrnix.lib.mkBufrnixPackage { + inherit pkgs; + config = { + root = ./.; + protoc = { + sourceDirectories = ["./proto"]; + includeDirectories = ["./proto"]; + }; + languages.proto = { + enable = true; + copier = { + enable = true; + outputPath = ["output/flattened"]; + preserveStructure = false; + flattenFiles = true; + includePatterns = ["*.proto"]; + excludePatterns = ["**/test/**"]; + filePrefix = "proto_"; + fileSuffix = "_v1"; + }; + }; + }; + }; + + # Selective copying: Only API and common types + api-only-copy = bufrnix.lib.mkBufrnixPackage { + inherit pkgs; + config = { + root = ./.; + protoc = { + sourceDirectories = ["./proto"]; + includeDirectories = ["./proto"]; + files = [ + "./proto/api/v1/user_service.proto" + "./proto/common/v1/types.proto" + ]; + }; + languages.proto = { + enable = true; + copier = { + enable = true; + outputPath = [ + "output/api-client" + "output/sdk-generator" + ]; + preserveStructure = true; + includePatterns = ["*.proto"]; + excludePatterns = []; + }; + }; + }; + }; + + # Multi-language example: Proto copying + Go generation + proto-and-go = bufrnix.lib.mkBufrnixPackage { + inherit pkgs; + config = { + root = ./.; + protoc = { + sourceDirectories = ["./proto"]; + includeDirectories = ["./proto"]; + }; + languages = { + # Copy proto files for distribution + proto.copier = { + enable = true; + outputPath = [ + "output/proto-dist" + "clients/proto" + ]; + preserveStructure = true; + includePatterns = ["*.proto"]; + excludePatterns = [ + "**/internal/**" + "**/test/**" + ]; + }; + + # Generate Go code from the same proto files + go = { + enable = true; + outputPath = "output/go"; + grpc = { + enable = true; + }; + options = [ + "paths=source_relative" + "module=github.com/example/proto-go" + ]; + }; + }; + }; + }; + }; + + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + # Development tools + protobuf + buf + + # For testing the examples + tree + fd + + # For Go development (if using the multi-language example) + go + ]; + + shellHook = '' + echo "๐Ÿš€ Proto Copier Example Development Environment" + echo "" + echo "Available packages:" + echo " default - Basic proto copying with structure preservation" + echo " advanced-copy - Multiple destinations with filtering" + echo " flattened-copy - Flatten all proto files with prefixes/suffixes" + echo " api-only-copy - Copy only API and common types" + echo " proto-and-go - Proto copying + Go code generation" + echo "" + echo "Usage:" + echo " nix run # Run default package" + echo " nix run .#advanced-copy # Run advanced copy example" + echo " nix run .#flattened-copy # Run flattened copy example" + echo " tree output/ # View copied files" + echo "" + echo "Proto files available:" + find proto -name "*.proto" -type f | head -10 + ''; + }; + }); +} \ No newline at end of file diff --git a/examples/proto-copier/proto/api/v1/user_service.proto b/examples/proto-copier/proto/api/v1/user_service.proto new file mode 100644 index 0000000..11f7f68 --- /dev/null +++ b/examples/proto-copier/proto/api/v1/user_service.proto @@ -0,0 +1,85 @@ +syntax = "proto3"; + +package api.v1; + +import "common/v1/types.proto"; +import "google/protobuf/timestamp.proto"; + +// User service for managing user accounts +service UserService { + // Create a new user + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); + + // Get user by ID + rpc GetUser(GetUserRequest) returns (GetUserResponse); + + // List users with pagination + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); + + // Update user information + rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse); + + // Delete a user + rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse); +} + +message CreateUserRequest { + string email = 1; + string name = 2; + common.v1.UserRole role = 3; +} + +message CreateUserResponse { + string user_id = 1; + google.protobuf.Timestamp created_at = 2; +} + +message GetUserRequest { + string user_id = 1; +} + +message GetUserResponse { + User user = 1; +} + +message ListUsersRequest { + int32 page_size = 1; + string page_token = 2; + common.v1.UserRole role_filter = 3; +} + +message ListUsersResponse { + repeated User users = 1; + string next_page_token = 2; + int32 total_count = 3; +} + +message UpdateUserRequest { + string user_id = 1; + string name = 2; + common.v1.UserRole role = 3; +} + +message UpdateUserResponse { + User user = 1; + google.protobuf.Timestamp updated_at = 2; +} + +message DeleteUserRequest { + string user_id = 1; +} + +message DeleteUserResponse { + bool success = 1; + string message = 2; +} + +message User { + string id = 1; + string email = 2; + string name = 3; + common.v1.UserRole role = 4; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; + bool active = 7; +} \ No newline at end of file diff --git a/examples/proto-copier/proto/common/v1/types.proto b/examples/proto-copier/proto/common/v1/types.proto new file mode 100644 index 0000000..4700fa8 --- /dev/null +++ b/examples/proto-copier/proto/common/v1/types.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package common.v1; + +// Common types used across multiple services + +// User roles in the system +enum UserRole { + USER_ROLE_UNSPECIFIED = 0; + USER_ROLE_GUEST = 1; + USER_ROLE_USER = 2; + USER_ROLE_MODERATOR = 3; + USER_ROLE_ADMIN = 4; +} + +// Status codes for operations +enum Status { + STATUS_UNSPECIFIED = 0; + STATUS_SUCCESS = 1; + STATUS_PENDING = 2; + STATUS_FAILED = 3; + STATUS_CANCELLED = 4; +} + +// Pagination metadata +message PaginationInfo { + int32 page = 1; + int32 page_size = 2; + int32 total_pages = 3; + int32 total_items = 4; +} + +// Error details +message ErrorDetail { + string code = 1; + string message = 2; + string field = 3; +} + +// Standard response wrapper +message ResponseMetadata { + Status status = 1; + repeated ErrorDetail errors = 2; + PaginationInfo pagination = 3; +} + +// Address information +message Address { + string street = 1; + string city = 2; + string state = 3; + string postal_code = 4; + string country = 5; +} + +// Contact information +message ContactInfo { + string phone = 1; + string email = 2; + Address address = 3; +} \ No newline at end of file diff --git a/examples/proto-copier/proto/external/v1/webhook.proto b/examples/proto-copier/proto/external/v1/webhook.proto new file mode 100644 index 0000000..6f1151a --- /dev/null +++ b/examples/proto-copier/proto/external/v1/webhook.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package external.v1; + +import "common/v1/types.proto"; +import "google/protobuf/timestamp.proto"; + +// Webhook definitions for external integrations +service WebhookService { + // Register a new webhook + rpc RegisterWebhook(RegisterWebhookRequest) returns (RegisterWebhookResponse); + + // Test webhook delivery + rpc TestWebhook(TestWebhookRequest) returns (TestWebhookResponse); +} + +message RegisterWebhookRequest { + string url = 1; + repeated EventType events = 2; + string secret = 3; +} + +message RegisterWebhookResponse { + string webhook_id = 1; + google.protobuf.Timestamp created_at = 2; +} + +message TestWebhookRequest { + string webhook_id = 1; + EventType event_type = 2; +} + +message TestWebhookResponse { + bool success = 1; + int32 http_status = 2; + string response_body = 3; +} + +enum EventType { + EVENT_TYPE_UNSPECIFIED = 0; + EVENT_TYPE_USER_CREATED = 1; + EVENT_TYPE_USER_UPDATED = 2; + EVENT_TYPE_USER_DELETED = 3; + EVENT_TYPE_SYSTEM_ALERT = 4; +} + +// Webhook payload sent to external systems +message WebhookPayload { + string event_id = 1; + EventType event_type = 2; + google.protobuf.Timestamp timestamp = 3; + oneof data { + UserEventData user_event = 4; + SystemEventData system_event = 5; + } +} + +message UserEventData { + string user_id = 1; + string action = 2; + map metadata = 3; +} + +message SystemEventData { + string alert_type = 1; + string severity = 2; + string message = 3; +} \ No newline at end of file diff --git a/examples/proto-copier/proto/internal/v1/admin_service.proto b/examples/proto-copier/proto/internal/v1/admin_service.proto new file mode 100644 index 0000000..af6f6b4 --- /dev/null +++ b/examples/proto-copier/proto/internal/v1/admin_service.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package internal.v1; + +import "common/v1/types.proto"; + +// Internal admin service - should not be exposed publicly +service AdminService { + // Get system statistics + rpc GetSystemStats(GetSystemStatsRequest) returns (GetSystemStatsResponse); + + // Perform database maintenance + rpc PerformMaintenance(PerformMaintenanceRequest) returns (PerformMaintenanceResponse); + + // Bulk operations + rpc BulkDeleteUsers(BulkDeleteUsersRequest) returns (BulkDeleteUsersResponse); +} + +message GetSystemStatsRequest { + // Empty request +} + +message GetSystemStatsResponse { + int64 total_users = 1; + int64 active_users = 2; + int64 total_requests_today = 3; + double average_response_time_ms = 4; + map endpoint_stats = 5; +} + +message PerformMaintenanceRequest { + enum MaintenanceType { + MAINTENANCE_TYPE_UNSPECIFIED = 0; + MAINTENANCE_TYPE_CLEANUP = 1; + MAINTENANCE_TYPE_REINDEX = 2; + MAINTENANCE_TYPE_BACKUP = 3; + } + + MaintenanceType type = 1; + bool force = 2; +} + +message PerformMaintenanceResponse { + bool success = 1; + string message = 2; + int64 duration_seconds = 3; +} + +message BulkDeleteUsersRequest { + repeated string user_ids = 1; + bool permanent = 2; +} + +message BulkDeleteUsersResponse { + int32 deleted_count = 1; + repeated string failed_ids = 2; +} \ No newline at end of file diff --git a/examples/proto-copier/proto/test/test_user.proto b/examples/proto-copier/proto/test/test_user.proto new file mode 100644 index 0000000..986b9d2 --- /dev/null +++ b/examples/proto-copier/proto/test/test_user.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package test; + +import "api/v1/user_service.proto"; + +// Test proto file - should be excluded from production copies +service TestUserService { + rpc CreateTestUser(CreateTestUserRequest) returns (api.v1.CreateUserResponse); + rpc CleanupTestUsers(CleanupTestUsersRequest) returns (CleanupTestUsersResponse); +} + +message CreateTestUserRequest { + string test_scenario = 1; + map test_data = 2; +} + +message CleanupTestUsersRequest { + string test_scenario = 1; + bool force = 2; +} + +message CleanupTestUsersResponse { + int32 deleted_count = 1; +} \ No newline at end of file diff --git a/output/backend/proto/proto/api/v1/user_service.proto b/output/backend/proto/proto/api/v1/user_service.proto new file mode 100644 index 0000000..11f7f68 --- /dev/null +++ b/output/backend/proto/proto/api/v1/user_service.proto @@ -0,0 +1,85 @@ +syntax = "proto3"; + +package api.v1; + +import "common/v1/types.proto"; +import "google/protobuf/timestamp.proto"; + +// User service for managing user accounts +service UserService { + // Create a new user + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); + + // Get user by ID + rpc GetUser(GetUserRequest) returns (GetUserResponse); + + // List users with pagination + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); + + // Update user information + rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse); + + // Delete a user + rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse); +} + +message CreateUserRequest { + string email = 1; + string name = 2; + common.v1.UserRole role = 3; +} + +message CreateUserResponse { + string user_id = 1; + google.protobuf.Timestamp created_at = 2; +} + +message GetUserRequest { + string user_id = 1; +} + +message GetUserResponse { + User user = 1; +} + +message ListUsersRequest { + int32 page_size = 1; + string page_token = 2; + common.v1.UserRole role_filter = 3; +} + +message ListUsersResponse { + repeated User users = 1; + string next_page_token = 2; + int32 total_count = 3; +} + +message UpdateUserRequest { + string user_id = 1; + string name = 2; + common.v1.UserRole role = 3; +} + +message UpdateUserResponse { + User user = 1; + google.protobuf.Timestamp updated_at = 2; +} + +message DeleteUserRequest { + string user_id = 1; +} + +message DeleteUserResponse { + bool success = 1; + string message = 2; +} + +message User { + string id = 1; + string email = 2; + string name = 3; + common.v1.UserRole role = 4; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; + bool active = 7; +} \ No newline at end of file diff --git a/output/backend/proto/proto/common/v1/types.proto b/output/backend/proto/proto/common/v1/types.proto new file mode 100644 index 0000000..4700fa8 --- /dev/null +++ b/output/backend/proto/proto/common/v1/types.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package common.v1; + +// Common types used across multiple services + +// User roles in the system +enum UserRole { + USER_ROLE_UNSPECIFIED = 0; + USER_ROLE_GUEST = 1; + USER_ROLE_USER = 2; + USER_ROLE_MODERATOR = 3; + USER_ROLE_ADMIN = 4; +} + +// Status codes for operations +enum Status { + STATUS_UNSPECIFIED = 0; + STATUS_SUCCESS = 1; + STATUS_PENDING = 2; + STATUS_FAILED = 3; + STATUS_CANCELLED = 4; +} + +// Pagination metadata +message PaginationInfo { + int32 page = 1; + int32 page_size = 2; + int32 total_pages = 3; + int32 total_items = 4; +} + +// Error details +message ErrorDetail { + string code = 1; + string message = 2; + string field = 3; +} + +// Standard response wrapper +message ResponseMetadata { + Status status = 1; + repeated ErrorDetail errors = 2; + PaginationInfo pagination = 3; +} + +// Address information +message Address { + string street = 1; + string city = 2; + string state = 3; + string postal_code = 4; + string country = 5; +} + +// Contact information +message ContactInfo { + string phone = 1; + string email = 2; + Address address = 3; +} \ No newline at end of file diff --git a/output/backend/proto/proto/external/v1/webhook.proto b/output/backend/proto/proto/external/v1/webhook.proto new file mode 100644 index 0000000..6f1151a --- /dev/null +++ b/output/backend/proto/proto/external/v1/webhook.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package external.v1; + +import "common/v1/types.proto"; +import "google/protobuf/timestamp.proto"; + +// Webhook definitions for external integrations +service WebhookService { + // Register a new webhook + rpc RegisterWebhook(RegisterWebhookRequest) returns (RegisterWebhookResponse); + + // Test webhook delivery + rpc TestWebhook(TestWebhookRequest) returns (TestWebhookResponse); +} + +message RegisterWebhookRequest { + string url = 1; + repeated EventType events = 2; + string secret = 3; +} + +message RegisterWebhookResponse { + string webhook_id = 1; + google.protobuf.Timestamp created_at = 2; +} + +message TestWebhookRequest { + string webhook_id = 1; + EventType event_type = 2; +} + +message TestWebhookResponse { + bool success = 1; + int32 http_status = 2; + string response_body = 3; +} + +enum EventType { + EVENT_TYPE_UNSPECIFIED = 0; + EVENT_TYPE_USER_CREATED = 1; + EVENT_TYPE_USER_UPDATED = 2; + EVENT_TYPE_USER_DELETED = 3; + EVENT_TYPE_SYSTEM_ALERT = 4; +} + +// Webhook payload sent to external systems +message WebhookPayload { + string event_id = 1; + EventType event_type = 2; + google.protobuf.Timestamp timestamp = 3; + oneof data { + UserEventData user_event = 4; + SystemEventData system_event = 5; + } +} + +message UserEventData { + string user_id = 1; + string action = 2; + map metadata = 3; +} + +message SystemEventData { + string alert_type = 1; + string severity = 2; + string message = 3; +} \ No newline at end of file diff --git a/output/frontend/src/proto/proto/api/v1/user_service.proto b/output/frontend/src/proto/proto/api/v1/user_service.proto new file mode 100644 index 0000000..11f7f68 --- /dev/null +++ b/output/frontend/src/proto/proto/api/v1/user_service.proto @@ -0,0 +1,85 @@ +syntax = "proto3"; + +package api.v1; + +import "common/v1/types.proto"; +import "google/protobuf/timestamp.proto"; + +// User service for managing user accounts +service UserService { + // Create a new user + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); + + // Get user by ID + rpc GetUser(GetUserRequest) returns (GetUserResponse); + + // List users with pagination + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); + + // Update user information + rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse); + + // Delete a user + rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse); +} + +message CreateUserRequest { + string email = 1; + string name = 2; + common.v1.UserRole role = 3; +} + +message CreateUserResponse { + string user_id = 1; + google.protobuf.Timestamp created_at = 2; +} + +message GetUserRequest { + string user_id = 1; +} + +message GetUserResponse { + User user = 1; +} + +message ListUsersRequest { + int32 page_size = 1; + string page_token = 2; + common.v1.UserRole role_filter = 3; +} + +message ListUsersResponse { + repeated User users = 1; + string next_page_token = 2; + int32 total_count = 3; +} + +message UpdateUserRequest { + string user_id = 1; + string name = 2; + common.v1.UserRole role = 3; +} + +message UpdateUserResponse { + User user = 1; + google.protobuf.Timestamp updated_at = 2; +} + +message DeleteUserRequest { + string user_id = 1; +} + +message DeleteUserResponse { + bool success = 1; + string message = 2; +} + +message User { + string id = 1; + string email = 2; + string name = 3; + common.v1.UserRole role = 4; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; + bool active = 7; +} \ No newline at end of file diff --git a/output/frontend/src/proto/proto/common/v1/types.proto b/output/frontend/src/proto/proto/common/v1/types.proto new file mode 100644 index 0000000..4700fa8 --- /dev/null +++ b/output/frontend/src/proto/proto/common/v1/types.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package common.v1; + +// Common types used across multiple services + +// User roles in the system +enum UserRole { + USER_ROLE_UNSPECIFIED = 0; + USER_ROLE_GUEST = 1; + USER_ROLE_USER = 2; + USER_ROLE_MODERATOR = 3; + USER_ROLE_ADMIN = 4; +} + +// Status codes for operations +enum Status { + STATUS_UNSPECIFIED = 0; + STATUS_SUCCESS = 1; + STATUS_PENDING = 2; + STATUS_FAILED = 3; + STATUS_CANCELLED = 4; +} + +// Pagination metadata +message PaginationInfo { + int32 page = 1; + int32 page_size = 2; + int32 total_pages = 3; + int32 total_items = 4; +} + +// Error details +message ErrorDetail { + string code = 1; + string message = 2; + string field = 3; +} + +// Standard response wrapper +message ResponseMetadata { + Status status = 1; + repeated ErrorDetail errors = 2; + PaginationInfo pagination = 3; +} + +// Address information +message Address { + string street = 1; + string city = 2; + string state = 3; + string postal_code = 4; + string country = 5; +} + +// Contact information +message ContactInfo { + string phone = 1; + string email = 2; + Address address = 3; +} \ No newline at end of file diff --git a/output/frontend/src/proto/proto/external/v1/webhook.proto b/output/frontend/src/proto/proto/external/v1/webhook.proto new file mode 100644 index 0000000..6f1151a --- /dev/null +++ b/output/frontend/src/proto/proto/external/v1/webhook.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package external.v1; + +import "common/v1/types.proto"; +import "google/protobuf/timestamp.proto"; + +// Webhook definitions for external integrations +service WebhookService { + // Register a new webhook + rpc RegisterWebhook(RegisterWebhookRequest) returns (RegisterWebhookResponse); + + // Test webhook delivery + rpc TestWebhook(TestWebhookRequest) returns (TestWebhookResponse); +} + +message RegisterWebhookRequest { + string url = 1; + repeated EventType events = 2; + string secret = 3; +} + +message RegisterWebhookResponse { + string webhook_id = 1; + google.protobuf.Timestamp created_at = 2; +} + +message TestWebhookRequest { + string webhook_id = 1; + EventType event_type = 2; +} + +message TestWebhookResponse { + bool success = 1; + int32 http_status = 2; + string response_body = 3; +} + +enum EventType { + EVENT_TYPE_UNSPECIFIED = 0; + EVENT_TYPE_USER_CREATED = 1; + EVENT_TYPE_USER_UPDATED = 2; + EVENT_TYPE_USER_DELETED = 3; + EVENT_TYPE_SYSTEM_ALERT = 4; +} + +// Webhook payload sent to external systems +message WebhookPayload { + string event_id = 1; + EventType event_type = 2; + google.protobuf.Timestamp timestamp = 3; + oneof data { + UserEventData user_event = 4; + SystemEventData system_event = 5; + } +} + +message UserEventData { + string user_id = 1; + string action = 2; + map metadata = 3; +} + +message SystemEventData { + string alert_type = 1; + string severity = 2; + string message = 3; +} \ No newline at end of file diff --git a/src/languages/proto/copier.nix b/src/languages/proto/copier.nix new file mode 100644 index 0000000..6613b3d --- /dev/null +++ b/src/languages/proto/copier.nix @@ -0,0 +1,145 @@ +{ + pkgs, + config, + lib, + cfg ? config.languages.proto.copier, + ... +}: +with lib; let + # Define output path and options + outputPaths = if isString cfg.outputPath then [cfg.outputPath] else cfg.outputPath; + + # File patterns for inclusion/exclusion + includePatterns = cfg.includePatterns; + excludePatterns = cfg.excludePatterns; + + # File transformation options + preserveStructure = cfg.preserveStructure; + flattenFiles = cfg.flattenFiles; + filePrefix = cfg.filePrefix; + fileSuffix = cfg.fileSuffix; + + # Generate shell commands for copying files + generateCopyCommands = outputPath: '' + echo "Copying proto files to ${outputPath}..." + mkdir -p "${outputPath}" + + # Find proto files for copying + + # Find proto files using the same logic as protoc + proto_files="" + ${ + # Use the same source directory resolution as mkBufrnix + if config.protoc.sourceDirectories == [] then '' + if [ -d "${config.root}" ]; then + proto_files=$(find "${config.root}" -type f \( ${concatStringsSep " -o " (map (pattern: "-name '${pattern}'") includePatterns)} \)) + fi + '' else '' + # Find proto files from specified directories + ${concatMapStringsSep "\n" (dir: '' + # Resolve source directory relative to config root + abs_dir="${config.root}/${dir}" + if [ -d "$abs_dir" ]; then + proto_files="$proto_files $(find "$abs_dir" -type f \( ${concatStringsSep " -o " (map (pattern: "-name '${pattern}'") includePatterns)} \))" + fi + '') config.protoc.sourceDirectories} + '' + } + + # Apply exclude patterns and copy files + for proto_file in $proto_files; do + [[ -n "$proto_file" ]] || continue + + # Check exclude patterns + should_exclude=false + ${concatStringsSep "\n" (map (pattern: '' + if [[ "$proto_file" == ${pattern} ]]; then + should_exclude=true + fi + '') excludePatterns)} + + # Skip if excluded + if [[ "$should_exclude" == "true" ]]; then + continue + fi + [[ -n "$proto_file" ]] || continue + + ${if preserveStructure && !flattenFiles then '' + # Preserve directory structure - calculate relative path from source directory + rel_path=$(realpath --relative-to="${config.root}" "$proto_file" 2>/dev/null || echo "$proto_file") + target_dir="${outputPath}/$(dirname "$rel_path")" + target_file="${outputPath}/$rel_path" + + ${optionalString (filePrefix != "") '' + # Add file prefix + target_file="${outputPath}/$(dirname "$rel_path")/${filePrefix}$(basename "$rel_path")" + ''} + + ${optionalString (fileSuffix != "") '' + # Add file suffix (before extension) + base_name=$(basename "$rel_path" .proto) + target_file="${outputPath}/$(dirname "$rel_path")/$base_name${fileSuffix}.proto" + ''} + + mkdir -p "$target_dir" + '' else '' + # Flatten files (copy all to output root) + file_name=$(basename "$proto_file") + target_file="${outputPath}/$file_name" + + ${optionalString (filePrefix != "") '' + # Add file prefix + target_file="${outputPath}/${filePrefix}$file_name" + ''} + + ${optionalString (fileSuffix != "") '' + # Add file suffix (before extension) + base_name=$(basename "$file_name" .proto) + target_file="${outputPath}/$base_name${fileSuffix}.proto" + ''} + ''} + + cp "$proto_file" "$target_file" + echo "Copied: $proto_file -> $target_file" + done + ''; +in { + # Runtime dependencies for proto file copying + runtimeInputs = [ + pkgs.coreutils # for mkdir, cp, find, realpath + pkgs.findutils # for find + pkgs.gnugrep # for grep filtering + ]; + + # No protoc plugins needed for copying + protocPlugins = []; + + # Initialization hook for proto copier + initHooks = '' + # Create output directories for proto copier + ${concatStringsSep "\n" (map (outputPath: '' + echo "Initializing proto copier output directory: ${outputPath}" + mkdir -p "${outputPath}" + '') outputPaths)} + ''; + + # Code generation hook for proto copier + generateHooks = '' + # Proto file copying operations + echo "Starting proto file copying..." + + ${concatStringsSep "\n" (map generateCopyCommands outputPaths)} + + echo "Proto file copying completed." + ''; + + # Alternative: Run copying in init hooks to avoid protoc dependency + copyInInitHooks = '' + # Proto file copying operations (run in init to avoid protoc) + echo "Starting proto file copying (init phase)..." + + ${concatStringsSep "\n" (map generateCopyCommands outputPaths)} + + echo "Proto file copying completed (init phase)." + ''; +} \ No newline at end of file diff --git a/src/languages/proto/default.nix b/src/languages/proto/default.nix new file mode 100644 index 0000000..d02155b --- /dev/null +++ b/src/languages/proto/default.nix @@ -0,0 +1,76 @@ +{ + pkgs, + config, + lib, + cfg ? config.languages.proto, + ... +}: +with lib; let + # Define output path and options + outputPath = cfg.outputPath or "proto"; + + # Import proto-specific sub-modules + copierModule = import ./copier.nix { + inherit pkgs lib; + cfg = + cfg.copier + // { + outputPath = cfg.copier.outputPath or outputPath; + }; + config = config; + }; + + # Combine all sub-modules + combineModuleAttrs = attr: + concatLists (catAttrs attr [ + copierModule + ]); +in { + # Runtime dependencies for proto operations + runtimeInputs = + [ + # Base dependencies (none needed for proto copying) + ] + ++ (combineModuleAttrs "runtimeInputs"); + + # Protoc plugin configuration for proto operations + protocPlugins = + [ + # Dummy protoc plugin to avoid "Missing output directives" error + # We don't actually want protoc to generate anything, just copy files + "--descriptor_set_out=/tmp/bufrnix-proto-dummy/descriptor.pb" + ] + ++ (combineModuleAttrs "protocPlugins"); + + # Initialization hook for proto operations + initHooks = + '' + # Create proto-specific directories + mkdir -p "${outputPath}" + echo "Initializing proto operations..." + + # Create temporary directory for dummy protoc output + mkdir -p "/tmp/bufrnix-proto-dummy" + '' + + concatStrings (catAttrs "initHooks" [ + copierModule + ]) + + (copierModule.copyInInitHooks or ""); + + # Code generation hook for proto operations + generateHooks = + '' + # Proto-specific operations + echo "Starting proto operations..." + mkdir -p ${outputPath} + '' + + concatStrings (catAttrs "generateHooks" [ + copierModule + ]) + + '' + + # Cleanup dummy protoc output + echo "Cleaning up temporary protoc output..." + rm -rf "/tmp/bufrnix-proto-dummy" + ''; +} \ No newline at end of file diff --git a/src/lib/bufrnix-options.nix b/src/lib/bufrnix-options.nix index ee90482..204e3d9 100644 --- a/src/lib/bufrnix-options.nix +++ b/src/lib/bufrnix-options.nix @@ -1631,6 +1631,136 @@ with lib; { }; }; + # Proto language options (file operations, copying, etc.) + proto = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable proto file operations (copying, etc.)"; + }; + + outputPath = mkOption { + type = types.either types.str (types.listOf types.str); + default = "proto"; + description = "Default output directory(ies) for proto operations"; + example = literalExpression '' + [ + "backend/proto" + "frontend/src/proto" + ] + ''; + }; + + files = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = "Proto files to operate on. Overrides global protoc.files. If null, uses global protoc.files."; + example = [ + "./proto/api/v1/user_service.proto" + "./proto/common/v1/types.proto" + ]; + }; + + additionalFiles = mkOption { + type = types.listOf types.str; + default = []; + description = "Additional proto files to operate on. Extends global protoc.files."; + example = [ + "./proto/internal/v1/admin_service.proto" + "./proto/mobile/v1/push_notifications.proto" + ]; + }; + + # Proto copier sub-module for copying proto files + copier = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable proto file copying"; + }; + + outputPath = mkOption { + type = types.either types.str (types.listOf types.str); + default = ["proto/copy"]; + description = "Output directory(ies) for copied proto files"; + example = literalExpression '' + [ + "backend/proto" + "frontend/src/proto" + "shared/proto" + ] + ''; + }; + + preserveStructure = mkOption { + type = types.bool; + default = true; + description = "Preserve directory structure when copying proto files"; + }; + + flattenFiles = mkOption { + type = types.bool; + default = false; + description = "Flatten all proto files into output directory root (overrides preserveStructure)"; + }; + + includePatterns = mkOption { + type = types.listOf types.str; + default = ["*.proto"]; + description = "File patterns to include when copying proto files"; + example = [ + "*.proto" + "api/**/*.proto" + "common/**/*.proto" + ]; + }; + + excludePatterns = mkOption { + type = types.listOf types.str; + default = []; + description = "File patterns to exclude when copying proto files"; + example = [ + "**/internal/**" + "**/test/**" + "**/*_test.proto" + ]; + }; + + filePrefix = mkOption { + type = types.str; + default = ""; + description = "Prefix to add to copied proto file names"; + example = "external_"; + }; + + fileSuffix = mkOption { + type = types.str; + default = ""; + description = "Suffix to add to copied proto file names (before .proto extension)"; + example = "_copy"; + }; + + files = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = "Proto files to copy. Overrides parent proto.files and global protoc.files. If null, inherits from parent."; + example = [ + "./proto/api/v1/user_service.proto" + "./proto/common/v1/types.proto" + ]; + }; + + additionalFiles = mkOption { + type = types.listOf types.str; + default = []; + description = "Additional proto files to copy. Extends inherited file list."; + example = [ + "./proto/extras/v1/optional_service.proto" + ]; + }; + }; + }; + # C# language options csharp = { enable = mkOption { diff --git a/src/lib/mkBufrnix.nix b/src/lib/mkBufrnix.nix index 535ad7e..7ad8326 100644 --- a/src/lib/mkBufrnix.nix +++ b/src/lib/mkBufrnix.nix @@ -138,6 +138,10 @@ with pkgs.lib; let scala = { package = pkgs.callPackage ../packages/scalapb {}; }; + proto = { + # Proto operations don't need special packages - just coreutils + # Package defaults are handled by the copier sub-module + }; }; };