* feat(routes): make relay base url configurable fixes #17 * chore(routes): update wiki to incorporate `routes.apiBaseUrl` config
1214 lines
33 KiB
Markdown
1214 lines
33 KiB
Markdown
# Contributor Guide
|
|
|
|
Welcome to the Nimbus contributor guide. This document provides everything you need to understand Nimbus's architecture and contribute effectively.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
**Understanding Nimbus**
|
|
- [Architecture Overview](#architecture-overview)
|
|
- [Core Concepts](#core-concepts)
|
|
- [Design Principles](#design-principles)
|
|
|
|
**Getting Started**
|
|
- [Development Environment Setup](#development-environment-setup)
|
|
- [Development Workflow](#development-workflow)
|
|
- [Testing Strategy](#testing-strategy)
|
|
|
|
**Contributing**
|
|
- [Contributing Process](#contributing-process)
|
|
- [Pull Request Guidelines](#pull-request-guidelines)
|
|
|
|
**Reference**
|
|
- [Coding Standards](#coding-standards)
|
|
- [Style Guides](#style-guides)
|
|
- [Architecture Rules](#architecture-rules)
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
Nimbus follows a modular architecture that separates concerns into distinct, focused modules. The package automatically extracts validation schemas from Laravel routes and provides an interface for testing them.
|
|
|
|
### High-Level Architecture
|
|
|
|
```mermaid
|
|
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
|
|
graph TB
|
|
subgraph "Laravel Application"
|
|
LaravelRoutes[Routes] --> LaravelControllers[Controllers]
|
|
LaravelControllers -.-> ValidationRules[Inline Validation Rules]
|
|
LaravelControllers -.-> FormRequests[Form Requests]
|
|
end
|
|
|
|
subgraph "Schema Initialization"
|
|
RouteExtractor[Rules Extractor] -->|"Passes Normalized Rules"| SchemaBuilder[Schema Builder]
|
|
SchemaBuilder ----->|"produces"| JSONSchema[JSON Schemas]
|
|
end
|
|
|
|
RouteExtractor -.->|"Inspects"| ValidationRules
|
|
RouteExtractor -.->|"Inspects"| FormRequests
|
|
RouteExtractor -.->|"reads"| LaravelRoutes
|
|
|
|
subgraph "HTTP Client"
|
|
VueFrontend[Vue Frontend \n <small>HTTP Client</small>]
|
|
VueFrontend -->|Makes Request| RequestRelay[Request Relay]
|
|
RequestRelay --> |"Builds"| HTTPResponses["Relay Response"] -->VueFrontend
|
|
end
|
|
|
|
JSONSchema --> VueFrontend
|
|
|
|
style LaravelRoutes fill:#ffffff,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style RouteExtractor fill:#f4f4f5,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style SchemaBuilder fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style VueFrontend fill:#d4d4d8,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style RequestRelay fill:#a1a1aa,stroke:#71717a,stroke-width:2px,color:#ffffff
|
|
```
|
|
|
|
### Module Structure
|
|
|
|
#### Backend Modules
|
|
|
|
```
|
|
src/
|
|
├── Commands/ # Artisan commands
|
|
├── Http/ # Controllers and middleware
|
|
├── Intellisenses/ # TypeScript interface generation
|
|
└── Modules/ # Core business logic
|
|
├── Config/ # Configuration enums and constants
|
|
├── Routes/ # Route extraction and normalization
|
|
├── Schemas/ # Schema generation and transformation
|
|
└── Relay/ # HTTP request relay and authorization
|
|
```
|
|
|
|
**Module responsibilities:**
|
|
|
|
- **Config** - Application configuration, enums, and constants.
|
|
- **Routes** - Extract and normalize Laravel routes and validation rules.
|
|
- **Schemas** - Transform validation rules into JSON schemas.
|
|
- **Relay** - Forward HTTP requests and handle authentication.
|
|
- **Intellisenses** - Generate TypeScript interfaces for backend entities.
|
|
|
|
#### Frontend Structure
|
|
|
|
```
|
|
resources/js/
|
|
├── app/ # Application setup and initialization
|
|
├── components/ # Vue components
|
|
│ ├── base/ # Reusable UI components (buttons, inputs)
|
|
│ ├── common/ # Shared components (modals, dropdowns)
|
|
│ ├── domain/ # Business logic components (request builder)
|
|
│ └── layout/ # Layout components (panels, navigation)
|
|
├── composables/ # Vue composables (reactive logic)
|
|
├── stores/ # Pinia stores (global state)
|
|
├── utils/ # Utility functions
|
|
└── interfaces/ # TypeScript interfaces
|
|
```
|
|
|
|
---
|
|
|
|
## Core Concepts
|
|
|
|
### Route Extraction Process
|
|
|
|
The route extraction process analyzes Laravel routes and extracts normalized validation schemas.
|
|
|
|
```mermaid
|
|
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
|
|
flowchart TD
|
|
LaravelRoutes[Laravel Routes] -->|"filter by prefix"| RouteFiltering[Route Filtering]
|
|
RouteFiltering --> TransformRoute[Normalize Route into `ExtractableRoute` object]
|
|
|
|
subgraph SchemaLoop["Loop over ExtractableRoute objects"]
|
|
TransformRoute --> SchemaExtraction[Schema Extraction]
|
|
SchemaExtraction -->|"find matching strategy"| StrategySelection[Strategy Selection]
|
|
StrategySelection -->|"generate JSON schema"| SchemaGeneration[Schema Generation]
|
|
SchemaGeneration --> JSONSchemaOutput[ExtractedRoute object Output]
|
|
end
|
|
|
|
RouteFiltering -..-> Fork1@{shape: "fork"}
|
|
Fork1 --> PrefixFilter[Apply Prefix Filter]
|
|
Fork1 --> IgnoredRoutesSub[Exclude Ignored Routes]
|
|
|
|
style LaravelRoutes fill:#ffffff,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style JSONSchemaOutput fill:#f4f4f5,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style SchemaExtraction fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style SchemaLoop fill:#f9fafb,stroke:#d4d4d8,stroke-dasharray: 4 2
|
|
```
|
|
|
|
**Key steps:**
|
|
|
|
1. **Route Filtering** - Filter routes by configured prefix, exclude ignored routes.
|
|
2. **Normalization** - Transform Laravel routes into `ExtractableRoute` objects.
|
|
3. **Strategy Selection** - Choose appropriate extraction strategy (Form Request vs inline validation).
|
|
4. **Schema Generation** - Convert validation rules to JSON schema.
|
|
5. **Output** - Produce `ExtractedRoute` objects with complete metadata.
|
|
|
|
### Schema Building Process
|
|
|
|
The schema builder converts Laravel validation rules into JSON Schema format with support for nested structures.
|
|
|
|
```mermaid
|
|
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
|
|
flowchart TD
|
|
ValidationRules[Validation Rules] --> SortedRules[Sort by processing order]
|
|
|
|
SortedRules --> FieldType
|
|
|
|
subgraph FieldLoop["Loop over sorted fields"]
|
|
FieldType{Field is of type:}
|
|
|
|
FieldType --> |Root Field| RootProperty[Build Root Property]
|
|
FieldType --> |Dot Notation Field| NestedStructure[Nested Object/Array]
|
|
FieldType --> |Array of Primitives| ArrayProperty[Build Array Items Schema]
|
|
|
|
NestedStructure -->|"recurse over segments"| SegmentType
|
|
|
|
SegmentType{Segment Type}
|
|
|
|
SegmentType -->|Other| IntermediateObjects["Add Object Property <br /><small>(if Missing)</small>"]
|
|
SegmentType -->|`*` segment| ArrayItems[Array Item Creation]
|
|
SegmentType -->|leaf segment| LeafProperty[Build Leaf Property]
|
|
end
|
|
|
|
FieldLoop --> FinalSchema[Output Final Schema]
|
|
|
|
style ValidationRules fill:#ffffff,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style FinalSchema fill:#f4f4f5,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style NestedStructure fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style ArrayProperty fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
style RootProperty fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
|
|
```
|
|
|
|
**Key concepts:**
|
|
|
|
- **Root fields** - Top-level properties (`name`, `email`).
|
|
- **Dot notation** - Nested structures (`user.profile.name`).
|
|
- **Array wildcards** - Array items (`items.*.name`).
|
|
- **Property building** - Convert validation rules to JSON Schema properties.
|
|
- **Recursive processing** - Handle deeply nested structures.
|
|
|
|
### Request Flow
|
|
|
|
How user interactions translate to API calls through the relay system.
|
|
|
|
```mermaid
|
|
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
|
|
sequenceDiagram
|
|
participant User
|
|
participant VueFrontend as Vue Frontend
|
|
participant LaravelBackend as Laravel Backend
|
|
participant APIEndpoint as API Endpoint
|
|
|
|
critical Initialization
|
|
LaravelBackend->>VueFrontend: Populate Routes with Schemas
|
|
end
|
|
|
|
User->>VueFrontend: Select Route
|
|
VueFrontend->>User: Display Route on UI
|
|
|
|
User->>VueFrontend: Configure Request
|
|
User->>VueFrontend: Trigger Request
|
|
VueFrontend->>VueFrontend: Prepare request for relaying
|
|
VueFrontend->>LaravelBackend: Send Request to Relay Endpoint
|
|
LaravelBackend->>APIEndpoint: Relay HTTP Request
|
|
APIEndpoint->>LaravelBackend: Return Response
|
|
LaravelBackend->>LaravelBackend: Prepare endpoint response into relay response
|
|
LaravelBackend->>VueFrontend: Return relayed response
|
|
VueFrontend->>User: Display Response
|
|
```
|
|
|
|
---
|
|
|
|
## Design Principles
|
|
|
|
### Single Responsibility Principle
|
|
|
|
Each class has one clear purpose and reason to change.
|
|
|
|
**Examples:**
|
|
|
|
- `RouteExtractorService` - Orchestrates route extraction.
|
|
- `SchemaBuilder` - Converts validation rules to schemas.
|
|
- `RequestRelayAction` - Handles HTTP request forwarding.
|
|
- `PropertyBuilder` - Builds individual schema properties.
|
|
|
|
### Dependency Injection
|
|
|
|
Services are injected rather than instantiated directly.
|
|
|
|
```php
|
|
class RouteExtractorService
|
|
{
|
|
public function __construct(
|
|
protected SchemaExtractor $schemaExtractor,
|
|
protected ExtractableRouteFactory $routeFactory,
|
|
protected IgnoredRoutesService $ignoredRoutesService,
|
|
) {}
|
|
}
|
|
```
|
|
|
|
### Immutable Value Objects
|
|
|
|
Data structures don't change after creation, ensuring predictable behavior.
|
|
|
|
```php
|
|
readonly class ExtractedRoute
|
|
{
|
|
public function __construct(
|
|
public readonly Endpoint $uri,
|
|
public readonly array $methods,
|
|
public readonly Schema $schema,
|
|
) {}
|
|
}
|
|
```
|
|
|
|
### Strategy Pattern
|
|
|
|
Different algorithms encapsulated in separate classes for flexibility.
|
|
|
|
**Examples:**
|
|
|
|
- `FormRequestExtractorStrategy` - Extracts rules from Form Request classes.
|
|
- `InlineRequestValidatorExtractorStrategy` - Extracts inline validation.
|
|
- `BearerAuthorizationHandler` - Handles Bearer token auth.
|
|
- `CurrentUserAuthorizationHandler` - Handles session-based auth.
|
|
|
|
### Actions vs Services
|
|
|
|
The codebase distinguishes between **Actions** and **Services** based on their responsibilities.
|
|
|
|
#### Actions
|
|
|
|
**Definition:** A single, composable unit of work that performs one discrete operation.
|
|
|
|
**Characteristics:**
|
|
- Single responsibility - performs one complete operation.
|
|
- Composable - can be called by other actions, services, or controllers.
|
|
- Stateless - only holds dependencies, no internal mutable state.
|
|
- Returns results - produces DTOs, value objects, or collections.
|
|
- Workflow-oriented - may orchestrate multiple steps internally.
|
|
|
|
**Example:**
|
|
```php
|
|
class RequestRelayAction
|
|
{
|
|
public function execute(RelayRequest $request): RelayResponse
|
|
{
|
|
// Performs one complete workflow: relay HTTP request
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Services
|
|
|
|
**Definition:** A reusable helper that encapsulates domain-specific logic or manages data.
|
|
|
|
**Characteristics:**
|
|
- Reusable logic - provides methods used by multiple actions or controllers.
|
|
- May hold state - if part of its responsibility.
|
|
- Non-orchestrating - it does not perform complete workflows.
|
|
- Domain/infrastructure focused - manages persistence, caching, configuration.
|
|
|
|
**Example:**
|
|
```php
|
|
class IgnoredRoutesService
|
|
{
|
|
public function isIgnored(string $routeName): bool
|
|
{
|
|
// Provides reusable logic for route filtering
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Quick Comparison
|
|
|
|
| Feature | Action | Service |
|
|
|---------|--------|-----------------------------------------|
|
|
| **Purpose** | Single unit of work / workflow | Reusable domain or infrastructure logic |
|
|
| **State** | Stateless beyond dependencies | Can hold state if necessary |
|
|
| **Return** | Value, DTO, collection | Self, Side-effect, or data |
|
|
| **Orchestration** | Can orchestrate multiple steps | Does not orchestrate |
|
|
| **Composable** | Yes | Usually used by actions or controllers |
|
|
|
|
**Rule of Thumb:** If the class performs one discrete operation and returns a result, it's an **Action**. If the class provides reusable logic or manages state, it's a **Service**.
|
|
|
|
---
|
|
|
|
## Development Environment Setup
|
|
|
|
### Local Development Setup
|
|
|
|
#### 1. Clone the Package Repository
|
|
|
|
```bash
|
|
git clone https://github.com/sunchayn/nimbus.git
|
|
cd nimbus
|
|
composer install
|
|
npm install
|
|
```
|
|
|
|
#### 2. Clone the Development Repository
|
|
|
|
The dev repository provides a foundation for testing the package with a Laravel application.
|
|
|
|
```bash
|
|
git clone https://github.com/sunchayn/nimbus-dev.git
|
|
cd nimbus-dev
|
|
composer run setup
|
|
```
|
|
|
|
#### 3. Start a Web Server
|
|
|
|
**Important:** The relay endpoint requires a real web server by default. Using `php artisan serve` alone won't work out of the box (check [#3.1](#31-using-it-without-a-real-server) for instructions on how to make it work).
|
|
|
|
**Suggeted Options:**
|
|
- **Laravel Herd** - Recommended for macOS.
|
|
- **Laravel Sail** - Docker-based environment.
|
|
- **Valet** - Nginx-based for macOS.
|
|
- **Docker/Nginx/Apache** - Manual setup.
|
|
|
|
**Why not the built-in server?**
|
|
|
|
PHP's built-in server is single-threaded. When the relay endpoint makes a request back to the application, it creates a deadlock where the server waits for itself to respond.
|
|
|
|
#### 3.1. Using it without a real server
|
|
|
|
If you need to run Nimbus without a dedicated web server, you can still use php artisan serve with a two-server setup.
|
|
|
|
A. In the nimbus-dev directory, start the main development process:
|
|
```bash
|
|
composer dev
|
|
```
|
|
|
|
B. In a separate terminal tab (or windo), start another PHP server:
|
|
```bash
|
|
php artisan serve
|
|
```
|
|
|
|
C. Once both are running, open your config/nimbus.php file and set the routes.apiBaseUrl value to the URL of the second server (the one started with php artisan serve).
|
|
|
|
```php
|
|
// Example.
|
|
'apiBaseUrl' => 'http://127.0.0.1:8000',
|
|
```
|
|
|
|
#### 4. Run Frontend Development Server
|
|
|
|
```bash
|
|
# Inside nimbus directory
|
|
npm run dev
|
|
```
|
|
|
|
#### 5. Access Nimbus
|
|
|
|
Navigate to your Laravel application's Nimbus endpoint:
|
|
|
|
```
|
|
http://your-app.test/nimbus
|
|
```
|
|
|
|
### Testing Different Laravel Versions
|
|
|
|
Use Composer scripts to switch between Laravel versions:
|
|
|
|
```bash
|
|
# Switch to Laravel 10
|
|
composer run switch:l10
|
|
|
|
# Switch to Laravel 11
|
|
composer run switch:l11
|
|
|
|
# Switch to Laravel 12
|
|
composer run switch:l12
|
|
```
|
|
|
|
These scripts update `orchestra/testbench` and all related dependencies automatically.
|
|
|
|
After switching, run tests to verify compatibility:
|
|
|
|
```bash
|
|
composer test:parallel
|
|
```
|
|
|
|
---
|
|
|
|
## Development Workflow
|
|
|
|
### Code Organization
|
|
|
|
Follow the established module structure when adding new functionality.
|
|
|
|
**Backend modules:**
|
|
|
|
```
|
|
src/Modules/
|
|
├── Config/ # Add new configuration enums here
|
|
├── Routes/ # Route extraction and normalization logic
|
|
├── Schemas/ # Schema generation and transformation
|
|
└── Relay/ # HTTP relay and authorization handlers
|
|
```
|
|
|
|
**Frontend structure:**
|
|
|
|
```
|
|
resources/js/
|
|
├── components/
|
|
│ ├── base/ # Reusable UI components (buttons, inputs)
|
|
│ ├── common/ # Shared components (modals, notifications)
|
|
│ ├── domain/ # Business logic components (request builder)
|
|
│ └── layout/ # Layout components (panels, sidebar)
|
|
├── composables/ # Reactive stateful logic
|
|
├── stores/ # Global state management (Pinia)
|
|
└── utils/ # Pure utility functions
|
|
```
|
|
|
|
### Git Workflow
|
|
|
|
#### 1. Fork and Clone
|
|
|
|
```bash
|
|
git clone https://github.com/YOUR_USERNAME/nimbus.git
|
|
cd nimbus
|
|
git remote add upstream https://github.com/sunchayn/nimbus.git
|
|
```
|
|
|
|
#### 2. Create Feature Branch
|
|
|
|
```bash
|
|
git checkout -b feature/your-feature-name origin/base
|
|
```
|
|
|
|
Use descriptive branch names:
|
|
- `feature/add-openapi-support`
|
|
- `fix/schema-generation-nested-arrays`
|
|
- `refactor/extract-property-builder`
|
|
|
|
#### 3. Make Changes
|
|
|
|
- Follow coding standards (see [Coding Standards](#coding-standards)).
|
|
- Write tests for new functionality.
|
|
- Update documentation if needed.
|
|
- Keep commits focused and atomic.
|
|
|
|
#### 4. Test Your Changes
|
|
|
|
```bash
|
|
# Backend tests
|
|
composer test
|
|
|
|
# Frontend tests
|
|
npm run test:run
|
|
|
|
# Linting
|
|
composer style:fix
|
|
npm run style:fix
|
|
```
|
|
|
|
#### 5. Commit Changes
|
|
|
|
Use conventional commit messages:
|
|
|
|
```bash
|
|
git add .
|
|
git commit -m "feat(schemas): add support for nested array validation"
|
|
```
|
|
|
|
**Commit format:** `type(scope): description`
|
|
|
|
**Types:**
|
|
- `feat` - New feature.
|
|
- `fix` - Bug fix.
|
|
- `refactor` - Code restructuring.
|
|
- `docs` - Documentation changes.
|
|
- `test` - Adding tests.
|
|
- `chore` - Maintenance tasks.
|
|
- `build` - Dependencies or CI changes.
|
|
|
|
#### 5. Push and Create Pull Request
|
|
|
|
```bash
|
|
git push -u origin feature/your-feature-name
|
|
```
|
|
|
|
Then create a pull request on GitHub with:
|
|
- Clear description of changes.
|
|
- Reference to related issues.
|
|
- Screenshots for UI changes.
|
|
- Testing instructions.
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Backend Testing
|
|
|
|
**Test organization:**
|
|
- Tests mirror the module structure.
|
|
- Each service has corresponding test classes.
|
|
- Integration tests verify end-to-end functionality.
|
|
- Unit tests focus on individual methods.
|
|
|
|
**Testing principles:**
|
|
- Test behavior, not implementation.
|
|
- Mock external dependencies.
|
|
- Use descriptive test names.
|
|
- Keep tests focused and independent.
|
|
|
|
**Running tests:**
|
|
|
|
```bash
|
|
# Run all tests
|
|
composer test
|
|
|
|
# Run tests in parallel (faster)
|
|
composer test:parallel
|
|
|
|
# Run with coverage
|
|
composer test:coverage
|
|
|
|
# Run specific test class
|
|
composer test -- --filter SchemaBuilderTest
|
|
|
|
# Run specific test method
|
|
composer test -- --filter testBuildsSchemaFromValidationRules
|
|
```
|
|
|
|
**Test example:**
|
|
|
|
```php
|
|
class SchemaBuilderUnitTest extends TestCase
|
|
{
|
|
public function test_it_builds_schema_for_simple_validation_rules(): void
|
|
{
|
|
// Arrange
|
|
|
|
$rules = [
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'email' => ['required', 'email'],
|
|
'age' => ['integer', 'min:18'],
|
|
];
|
|
|
|
// Act
|
|
|
|
$schema = $this->schemaBuilder->build($rules);
|
|
|
|
// Assert
|
|
|
|
$this->assertArrayHasKey('name', $schema['properties']);
|
|
$this->assertEquals('string', $schema['properties']['name']['type']);
|
|
$this->assertTrue($schema['properties']['name']['required']);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Frontend Testing
|
|
|
|
**Component testing guidelines:**
|
|
- Test user interactions and outcomes.
|
|
- Mock external dependencies (API calls, stores).
|
|
- Use `data-testid` attributes for selectors.
|
|
- Focus on component behavior, not implementation details.
|
|
|
|
**Linting and formatting:**
|
|
|
|
```bash
|
|
# Check styling issues
|
|
npm run style:check
|
|
|
|
# Automatically fix styling issues
|
|
npm run style:fix
|
|
```
|
|
|
|
**Running component tests:**
|
|
|
|
```bash
|
|
# Run all tests
|
|
npm run test:run
|
|
|
|
# Run tests in watch mode
|
|
npm run test
|
|
|
|
# Run specific test file
|
|
npm run test:run -- RequestBuilder.test.ts
|
|
```
|
|
|
|
**Test example:**
|
|
|
|
```typescript
|
|
import { mount } from '@vue/test-utils'
|
|
import { describe, it, expect } from 'vitest'
|
|
import RequestBuilder from '@/components/domain/RequestBuilder.vue'
|
|
|
|
describe('RequestBuilder', () => {
|
|
it('validates required fields', async () => {
|
|
const wrapper = mount(RequestBuilder, {
|
|
props: {
|
|
schema: {
|
|
properties: {
|
|
name: { type: 'string', required: true }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
// Trigger validation
|
|
await wrapper.find('[data-testid="submit-btn"]').trigger('click')
|
|
|
|
// Assert error message appears
|
|
expect(wrapper.find('[data-testid="error-message"]').text())
|
|
.toContain('name is required')
|
|
})
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Contributing Process
|
|
|
|
### Before You Start
|
|
|
|
1. **Check existing issues** - Someone may already be working on it.
|
|
2. **Open an issue first** - For major changes, discuss the approach.
|
|
3. **Read the documentation** - Understand the architecture and patterns.
|
|
4. **Set up your environment** - Follow the setup guide above.
|
|
|
|
### Development Process
|
|
|
|
Follow the [Git Workflow](#git-workflow) guide.
|
|
|
|
#### Update Documentation
|
|
|
|
If your changes affect:
|
|
- **User-facing features** → Update User Guide
|
|
- **Architecture or patterns** → Update Contributor Guide
|
|
- **Configuration** → Update config comments and README
|
|
|
|
#### Submit Pull Request
|
|
|
|
**PR title format:** `type(scope): brief description`
|
|
|
|
**PR description should include:**
|
|
- What changes were made.
|
|
- Why the changes were necessary.
|
|
- How to test the changes.
|
|
- Screenshots for UI changes.
|
|
- Related issues (e.g., "Closes #123").
|
|
|
|
**Example PR description:**
|
|
|
|
```markdown
|
|
## Description
|
|
Adds support for extracting validation rules from custom validation rule objects.
|
|
|
|
## Motivation
|
|
Currently, Nimbus only supports array-based validation rules. Many Laravel apps use custom rule objects, which are not extracted.
|
|
|
|
## Changes
|
|
- Added `RuleObjectExtractor` to handle custom rule objects
|
|
- Updated `SchemaExtractor` to detect and use the new extractor
|
|
- Added tests for rule object extraction
|
|
|
|
## Testing
|
|
1. Create a route with custom rule object validation
|
|
2. Access Nimbus interface
|
|
3. Verify the route appears with correct schema
|
|
|
|
Closes #42
|
|
```
|
|
|
|
### Pull Request Guidelines
|
|
|
|
#### Code Review Checklist
|
|
|
|
Before submitting, verify:
|
|
|
|
- [ ] Code follows established patterns and conventions.
|
|
- [ ] All tests pass.
|
|
- [ ] New functionality has test coverage.
|
|
- [ ] Documentation is updated.
|
|
- [ ] No linting or formatting errors.
|
|
- [ ] Commits are clean and well-described.
|
|
- [ ] PR description is clear and complete.
|
|
|
|
#### What Reviewers Look For
|
|
|
|
**Code quality:**
|
|
- Follows SOLID principles.
|
|
- Appropriate use of Actions vs Services.
|
|
- Proper dependency injection.
|
|
- Clear, descriptive naming.
|
|
|
|
**Testing:**
|
|
- Adequate test coverage.
|
|
- Tests focus on behavior, not implementation.
|
|
- Edge cases are considered.
|
|
- Tests are independent and repeatable.
|
|
|
|
**Documentation:**
|
|
- Code is self-documenting where possible.
|
|
- Complex logic has explanatory comments.
|
|
- User-facing changes documented in User Guide.
|
|
- Architecture changes documented in Contributor Guide.
|
|
|
|
**Performance:**
|
|
- No obvious performance issues.
|
|
- Efficient algorithms and data structures.
|
|
- Appropriate use of caching.
|
|
|
|
**Breaking changes:**
|
|
- Clearly documented.
|
|
- Migration path provided if applicable.
|
|
- Version bump follows semantic versioning.
|
|
|
|
---
|
|
|
|
## Coding Standards
|
|
|
|
### PHP Coding Standards
|
|
|
|
#### Method Naming
|
|
|
|
Use action-oriented, domain-specific names that clearly communicate intent.
|
|
|
|
**Good examples:**
|
|
|
|
```php
|
|
// Action-oriented + domain-specific
|
|
public function switchToRouteDefinitionOf(string $method): void
|
|
public function initializeRequest(Route $route, array $availableRoutes): void
|
|
public function executeCurrentRequest(): void
|
|
public function validateUserPreferences(array $data): bool
|
|
public function generateCurlCommand(Request $request): string
|
|
```
|
|
|
|
**Poor examples:**
|
|
|
|
```php
|
|
// Implementation-focused, unclear intent
|
|
public function updateSchemaAndPayloadForMethod(string $method): void
|
|
public function setupRequestData(Route $route, array $routes): void
|
|
public function processRequest(): void
|
|
public function doValidation(array $data): bool
|
|
```
|
|
|
|
#### Type Declarations
|
|
|
|
Always use type declarations for parameters and return types.
|
|
|
|
```php
|
|
// Good
|
|
public function buildSchema(array $rules): array
|
|
{
|
|
// Implementation
|
|
}
|
|
|
|
// Bad
|
|
public function buildSchema($rules)
|
|
{
|
|
// Implementation
|
|
}
|
|
```
|
|
|
|
#### Readonly Properties
|
|
|
|
Use readonly properties for immutable data.
|
|
|
|
```php
|
|
readonly class ExtractedRoute
|
|
{
|
|
public function __construct(
|
|
public readonly Endpoint $uri,
|
|
public readonly array $methods,
|
|
public readonly Schema $schema,
|
|
) {}
|
|
}
|
|
```
|
|
|
|
### Vue.js Coding Standards
|
|
|
|
#### Composition API
|
|
|
|
Always use `<script setup>` with TypeScript.
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
|
|
// Props
|
|
const props = defineProps<{
|
|
title: string
|
|
items: Item[]
|
|
}>()
|
|
|
|
// Emits
|
|
const emit = defineEmits<{
|
|
update: [value: string]
|
|
}>()
|
|
|
|
// State
|
|
const isLoading = ref(false)
|
|
|
|
// Computed
|
|
const filteredItems = computed(() => {
|
|
return props.items.filter(item => item.active)
|
|
})
|
|
|
|
// Methods
|
|
const handleUpdate = (value: string) => {
|
|
emit('update', value)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Template content -->
|
|
</template>
|
|
```
|
|
|
|
#### Component Structure Order
|
|
|
|
Organize component code in this order:
|
|
|
|
1. Imports.
|
|
2. Props and emits.
|
|
3. Reactive state (ref, reactive).
|
|
4. Computed properties.
|
|
5. Methods/functions.
|
|
6. Lifecycle hooks.
|
|
7. Watch statements.
|
|
|
|
#### TypeScript Usage
|
|
|
|
- Use strict type checking.
|
|
- Define interfaces for complex objects.
|
|
- Avoid `any` type.
|
|
- Use type assertions sparingly.
|
|
|
|
```typescript
|
|
// Good - explicit types
|
|
interface RequestConfig {
|
|
method: string
|
|
url: string
|
|
headers: Record<string, string>
|
|
body?: unknown
|
|
}
|
|
|
|
const config: RequestConfig = {
|
|
method: 'POST',
|
|
url: '/api/users',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
}
|
|
|
|
// Bad - any type
|
|
const config: any = { /* ... */ }
|
|
```
|
|
|
|
---
|
|
|
|
## Style Guides
|
|
|
|
### Comment Style Guide
|
|
|
|
#### Docblock Format
|
|
|
|
Use docblocks to explain purpose and business context, not implementation.
|
|
|
|
```php
|
|
/**
|
|
* Builds a JSON Schema from Laravel validation rules.
|
|
*
|
|
* This method handles nested structures using dot notation and array wildcards.
|
|
* It processes fields in a specific order to ensure parent objects exist before
|
|
* child properties are added.
|
|
*
|
|
* @param array<string, array<int, string|object>> $rules Laravel validation rules
|
|
* @return array<string, mixed> JSON Schema representation
|
|
*/
|
|
public function build(array $rules): array
|
|
{
|
|
// Implementation
|
|
}
|
|
```
|
|
|
|
#### Inline Comments
|
|
|
|
Explain **why** decisions were made, not **what** the code does.
|
|
|
|
```php
|
|
// Good - explains reasoning
|
|
// Process parent fields first to ensure nested objects exist
|
|
$sortedFields = $this->sortFieldsByDepth($fields);
|
|
|
|
// Bad - restates the obvious
|
|
// Loop through the fields
|
|
foreach ($fields as $field) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
#### TODO Comments
|
|
|
|
Use categorized TODO comments for future work.
|
|
|
|
```php
|
|
// TODO [Feature]: Add support for custom validation rule objects
|
|
// TODO [Enhancement]: Cache compiled schemas for better performance
|
|
// TODO [Bug]: Fix handling of conditional validation rules
|
|
// TODO [Refactor]: Extract cookie decryption into dedicated service
|
|
// TODO [Performance]: Optimize schema generation for large rule sets
|
|
// TODO [Documentation]: Add examples for complex nested structures
|
|
// TODO [Test]: Add integration tests for authentication flow
|
|
```
|
|
|
|
**Allowed categories:**
|
|
|
|
| Category | Purpose | Example |
|
|
|----------|---------|---------|
|
|
| **Feature** | New functionality planned | Investigate supporting Closures for route extraction |
|
|
| **Enhancement** | Improvement to existing functionality | Convert logged-in user to bearer token |
|
|
| **Bug** | Known defect that needs fixing | Implement impersonation logic properly |
|
|
| **Refactor** | Code reorganization needed | Extract cookie creation into service |
|
|
| **Performance** | Optimization opportunity | Cache authorization handlers |
|
|
| **Documentation** | Missing or incomplete docs | Add examples for custom auth strategies |
|
|
| **Test** | Missing or incomplete test coverage | Add integration tests for relay flow |
|
|
|
|
### Tailwind CSS Usage
|
|
|
|
#### Use Utility Classes Only
|
|
|
|
Use Tailwind's utility classes exclusively. Avoid custom CSS when possible.
|
|
|
|
```vue
|
|
<!-- Good - Tailwind utilities -->
|
|
<div class="flex h-screen max-h-screen overflow-hidden">
|
|
<div class="bg-white border-r border-zinc-200 p-4">
|
|
<h2 class="text-lg font-semibold text-zinc-900 mb-4">Routes</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bad - custom CSS -->
|
|
<div class="custom-layout">
|
|
<div class="sidebar">
|
|
<h2 class="sidebar-title">Routes</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.custom-layout { /* ... */ }
|
|
.sidebar { /* ... */ }
|
|
</style>
|
|
```
|
|
|
|
#### Component Class Extraction
|
|
|
|
For repeated patterns, extract to component props or composables, not CSS classes.
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
const buttonClasses = computed(() => ({
|
|
base: 'px-4 py-2 rounded font-medium transition-colors',
|
|
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
|
|
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900'
|
|
}))
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture Rules
|
|
|
|
### Backend Architecture Rules
|
|
|
|
#### Controllers
|
|
|
|
Controllers orchestrate, they don't contain business logic.
|
|
|
|
```php
|
|
// Good - controller delegates to action
|
|
class RelayController extends Controller
|
|
{
|
|
public function __construct(
|
|
private RequestRelayAction $relayAction
|
|
) {}
|
|
|
|
public function relay(Request $request): JsonResponse
|
|
{
|
|
$relayRequest = RelayRequest::fromRequest($request);
|
|
$relayResponse = $this->relayAction->execute($relayRequest);
|
|
|
|
return response()->json($relayResponse);
|
|
}
|
|
}
|
|
|
|
// Bad - controller contains business logic
|
|
class RelayController extends Controller
|
|
{
|
|
public function relay(Request $request): JsonResponse
|
|
{
|
|
// Complex logic here
|
|
$client = new Client();
|
|
$response = $client->request(/* ... */);
|
|
// More logic...
|
|
|
|
return response()->json($data);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Services
|
|
|
|
Services handle single business concerns and use dependency injection.
|
|
|
|
```php
|
|
class SchemaExtractorService
|
|
{
|
|
public function __construct(
|
|
private FormRequestExtractorStrategy $formRequestStrategy,
|
|
private InlineRequestValidatorExtractorStrategy $inlineStrategy,
|
|
) {}
|
|
|
|
public function extract(ExtractableRoute $route): ?array
|
|
{
|
|
// Single responsibility: extract validation rules
|
|
// Delegates to appropriate strategy
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Value Objects and DTOs
|
|
|
|
Use immutable value objects and DTOs for data transfer and manipulation.
|
|
|
|
```php
|
|
// Immutable value object
|
|
readonly class RelayRequest
|
|
{
|
|
public function __construct(
|
|
public string $method,
|
|
public string $url,
|
|
public array $headers,
|
|
public ?array $body,
|
|
public ?AuthorizationType $authorization,
|
|
) {}
|
|
|
|
public static function fromRequest(Request $request): self
|
|
{
|
|
return new self(
|
|
method: $request->input('method'),
|
|
url: $request->input('url'),
|
|
headers: $request->input('headers', []),
|
|
body: $request->input('body'),
|
|
authorization: AuthorizationType::tryFrom($request->input('authorization.type')),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Frontend Architecture Rules
|
|
|
|
#### Component Organization
|
|
|
|
Group related code together and keep components focused.
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
// 1. Imports
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useRequestStore } from '@/stores/request'
|
|
|
|
// 2. Props and emits
|
|
const props = defineProps<{
|
|
route: Route
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
submit: [request: RequestConfig]
|
|
}>()
|
|
|
|
// 3. Stores and composables
|
|
const requestStore = useRequestStore()
|
|
const { validatePayload } = useValidation()
|
|
|
|
// 4. Reactive state
|
|
const payload = ref<Record<string, unknown>>({})
|
|
const errors = ref<ValidationError[]>([])
|
|
|
|
// 5. Computed properties
|
|
const isValid = computed(() => errors.value.length === 0)
|
|
|
|
// 6. Methods
|
|
const handleSubmit = () => {
|
|
errors.value = validatePayload(payload.value)
|
|
if (isValid.value) {
|
|
emit('submit', { ...payload.value })
|
|
}
|
|
}
|
|
|
|
// 7. Lifecycle hooks
|
|
onMounted(() => {
|
|
// Initialize
|
|
})
|
|
</script>
|
|
```
|
|
|
|
#### State Management
|
|
|
|
Use appropriate state management strategies:
|
|
|
|
- **Local state** - Component-specific data.
|
|
- **Composables** - Reusable reactive logic.
|
|
- **Pinia stores** - Global application state.
|
|
|
|
```typescript
|
|
// Composable for reusable reactive logic
|
|
export function useValidation(schema: Schema) {
|
|
const errors = ref<ValidationError[]>([])
|
|
|
|
const validate = (data: unknown): boolean => {
|
|
errors.value = performValidation(data, schema)
|
|
return errors.value.length === 0
|
|
}
|
|
|
|
return {
|
|
errors: readonly(errors),
|
|
validate
|
|
}
|
|
}
|
|
|
|
// Store for global state
|
|
export const useRequestStore = defineStore('request', () => {
|
|
const currentRequest = ref<RequestConfig | null>(null)
|
|
const history = ref<RequestConfig[]>([])
|
|
|
|
const setCurrentRequest = (request: RequestConfig) => {
|
|
currentRequest.value = request
|
|
history.value.push(request)
|
|
}
|
|
|
|
return {
|
|
currentRequest,
|
|
history,
|
|
setCurrentRequest
|
|
}
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Final Notes
|
|
|
|
### Continuous Improvement
|
|
|
|
The codebase has undergone multiple refactoring rounds. Some inconsistencies or confusing parts may exist and will be addressed as the project matures.
|
|
|
|
### Getting Help
|
|
|
|
- **Architecture questions** - Open a discussion on GitHub.
|
|
- **Bug reports** - Open an issue with reproduction steps.
|
|
- **Feature proposals** - Start a discussion first to align on approach.
|
|
|
|
---
|
|
|
|
### Thank You
|