Skip to content
Merged

Dev2 #101

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ app.Run(30 * time.Second)
```go
// main.go
func main() {
// Auto-generates code when @RouterService changes detected
// Auto-generates code when @EndpointService changes detected
lokstra.Bootstrap()

// Import packages with @RouterService annotations
// Import packages with @EndpointService annotations
_ "myapp/modules/user/application"

// Services auto-registered via annotations!
Expand All @@ -49,15 +49,15 @@ func main() {
**Service with annotations:**

```go
// @RouterService name="user-service", prefix="/api/users"
// @EndpointService name="user-service", prefix="/api/users"
type UserService struct {
// @Inject "user-repository" - Direct service injection
UserRepo UserRepository

// @Inject "cfg:store.implementation" - Service from config (NEW!)
// @Inject "@store.implementation" - Service from config
Store Store // Injected service name from config: store.implementation = "postgres-store"

// @InjectCfgValue "app.name" - Config value injection
// @Inject "cfg:app.name" - Config value injection
AppName string
}// @Route "GET /{id}"
func (s *UserService) GetByID(p *GetUserParams) (*User, error) {
Expand Down Expand Up @@ -137,7 +137,7 @@ func (s *MySQLStore) GetUser(id string) (*User, error) { /* ... */ }

```go
// application/user_service.go
// @RouterService name="user-service", prefix="/api/users"
// @EndpointService name="user-service", prefix="/api/users"
type UserService struct {
// @Inject "@store.implementation"
Store Store // Actual service injected based on config!
Expand Down Expand Up @@ -301,7 +301,7 @@ myapp/
├── infrastructure/
│ └── user_repository.go
└── application/
├── user_service.go # Contains @RouterService
├── user_service.go # Contains @EndpointService
└── zz_generated.lokstra.go # Auto-generated
```

Expand Down Expand Up @@ -381,7 +381,7 @@ go run . --generate-only # Force rebuild all
3. **Use pointer parameters** for request binding: `*CreateUserParams`
4. **Follow domain-driven design**: domain → repository → service
5. **Type-safe DI**: Use direct type assertions and `service.LazyLoad[T]` for lazy service loading
6. **Prefer annotations** for business services: Use `@RouterService` + `@Route` instead of manual registration
6. **Prefer annotations** for business services: Use `@EndpointService` + `@Route` instead of manual registration

## When Suggesting Code

Expand All @@ -397,12 +397,12 @@ go run . --generate-only # Force rebuild all
- Include error handling
- Include validation tags
- Include config.yaml if using framework mode
- Use `@RouterService` annotations for business services
- Use `@EndpointService` annotations for business services

3. **Follow project structure:**
- Separate domain/application/infrastructure
- Use interfaces in domain layer
- Business logic in application layer with `@RouterService`
- Business logic in application layer with `@EndpointService`
- Data access in infrastructure layer

## Resources
Expand Down
8 changes: 4 additions & 4 deletions core/annotation/PRACTICAL_EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Your app needs different JWT secrets, database timeouts, and API keys per enviro
**Code (unchanged across environments):**

```go
// @RouterService name="auth-service", prefix="/api/auth"
// @EndpointService name="auth-service", prefix="/api/auth"
type AuthService struct {
// @Inject "user-repo"
UserRepo UserRepository
Expand Down Expand Up @@ -122,7 +122,7 @@ Different tenants use different database implementations (PostgreSQL, MySQL, Mon
### Solution: Service + Config Indirection

```go
// @RouterService name="tenant-service", prefix="/api/tenants"
// @EndpointService name="tenant-service", prefix="/api/tenants"
type TenantService struct {
// @Inject "@tenant.db-provider" // Service name from config
DB database.Provider
Expand Down Expand Up @@ -163,7 +163,7 @@ Switch tenant by changing `tenant.db-provider` to `database.tenant-b.provider`!
Different subscription plans have different rate limits.

```go
// @RouterService name="api-gateway", prefix="/api"
// @EndpointService name="api-gateway", prefix="/api"
type APIGateway struct {
// @Inject "cfg:@rate-limit.requests-per-minute"
RequestsPerMinute int
Expand Down Expand Up @@ -261,7 +261,7 @@ configs:
## Example 6: Cache Strategy Selection

```go
// @RouterService name="product-service", prefix="/api/products"
// @EndpointService name="product-service", prefix="/api/products"
type ProductService struct {
// @Inject "@cache.provider"
Cache cache.Provider
Expand Down
20 changes: 11 additions & 9 deletions core/annotation/arg_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func ParseFileAnnotations(path string) ([]*ParsedAnnotation, error) {
if after, ok := strings.CutPrefix(line, "//"); ok {
// CRITICAL: Detect code examples in Go documentation
// Go doc convention: use TAB after // for code examples
// Valid annotation: // @RouterService (space after //)
// Invalid annotation: // @RouterService (TAB after // - code example)
// Valid annotation: // @EndpointService (space after //)
// Invalid annotation: // @EndpointService (TAB after // - code example)

// Extract the content after //
commentStart := strings.Index(originalLine, "//")
Expand All @@ -55,8 +55,8 @@ func ParseFileAnnotations(path string) ([]*ParsedAnnotation, error) {
if strings.HasPrefix(trimmedAfter, "@") {
leadingWhitespace := afterComment[:len(afterComment)-len(trimmedAfter)]

// Allow single space (normal comment formatting: "// @RouterService")
// Reject TAB or multiple spaces (code examples: "// @RouterService" or "//\t@RouterService")
// Allow single space (normal comment formatting: "// @EndpointService")
// Reject TAB or multiple spaces (code examples: "// @EndpointService" or "//\t@EndpointService")
if len(leadingWhitespace) > 1 || (len(leadingWhitespace) == 1 && leadingWhitespace[0] == '\t') {
// Indented annotation - skip it (likely example code)
continue
Expand Down Expand Up @@ -108,18 +108,20 @@ func ParseFileAnnotations(path string) ([]*ParsedAnnotation, error) {
}

return annotations, nil
} // parseAnnotationLine parses a single annotation line
}

// parseAnnotationLine parses a single annotation line
// Supports both formats:
//
// @RouterService name="user-service", prefix="/api"
// @RouterService "user-service", "/api"
// @EndpointService name="user-service", prefix="/api"
// @EndpointService "user-service", "/api"
func parseAnnotationLine(line string, lineNum int) (*ParsedAnnotation, error) {
// Extract annotation name
parts := strings.SplitN(line, "(", 2)
if len(parts) == 1 {
// No parentheses - might have args without parens or no args
// @RouterService name="value"
// @RouterService
// @EndpointService name="value"
// @EndpointService
nameAndArgs := strings.TrimSpace(parts[0])
spaceIdx := strings.Index(nameAndArgs, " ")

Expand Down
Loading