diff --git a/internal/compiler/routes.go b/internal/compiler/routes.go index cde14a9..89c76db 100644 --- a/internal/compiler/routes.go +++ b/internal/compiler/routes.go @@ -183,6 +183,9 @@ func validateRouteMethodConflicts(pages []gwdkir.Page, endpoints []gwdkir.GoEndp if allowedPageOwnedQueryRouteConflict(previous, registration) { continue } + if identicalContractRouteRegistration(previous, registration) { + continue + } diagnostics = append(diagnostics, ValidationError{ Code: "route_method_conflict", PageID: registration.PageID, @@ -203,14 +206,15 @@ func validateRouteMethodConflicts(pages []gwdkir.Page, endpoints []gwdkir.GoEndp } type routeRegistration struct { - Kind string - Owner string - Method string - Route string - Pattern string - PageID string - Source string - Span source.SourceSpan + Kind string + Owner string + Method string + Route string + Pattern string + Contract string + PageID string + Source string + Span source.SourceSpan } func routeRegistrations(pages []gwdkir.Page, endpoints []gwdkir.GoEndpoint, refs []gwdkir.ContractReference) []routeRegistration { @@ -334,7 +338,8 @@ func routeRegistrations(pages []gwdkir.Page, endpoints []gwdkir.GoEndpoint, refs if err := source.ValidateBackendRoutePath(ref.Path); err != nil { continue } - info, issues := parseRoute(ref.Path) + route := source.BackendRoutePath(ref.Path) + info, issues := parseRoute(route) if len(issues) > 0 { continue } @@ -344,14 +349,15 @@ func routeRegistrations(pages []gwdkir.Page, endpoints []gwdkir.GoEndpoint, refs } method := strings.ToUpper(strings.TrimSpace(ref.Method)) registrations = append(registrations, routeRegistration{ - Kind: "contract_" + kind, - Owner: fmt.Sprintf("%s contract %s", kind, ref.Name), - Method: method, - Route: ref.Path, - Pattern: info.Pattern, - PageID: ref.OwnerID, - Source: ref.Source, - Span: ref.Span, + Kind: "contract_" + kind, + Owner: fmt.Sprintf("%s contract %s", kind, ref.Name), + Method: method, + Route: route, + Pattern: info.Pattern, + Contract: ref.Name, + PageID: ref.OwnerID, + Source: ref.Source, + Span: ref.Span, }) } return registrations @@ -361,6 +367,16 @@ func allowedPageOwnedQueryRouteConflict(first routeRegistration, current routeRe return pageOwnedQueryRouteConflict(first, current) || pageOwnedQueryRouteConflict(current, first) } +func identicalContractRouteRegistration(first routeRegistration, current routeRegistration) bool { + if !strings.HasPrefix(first.Kind, "contract_") || first.Kind != current.Kind { + return false + } + return first.Method == current.Method && + first.Route == current.Route && + first.Contract == current.Contract && + first.PageID == current.PageID +} + func pageOwnedQueryRouteConflict(page routeRegistration, query routeRegistration) bool { return page.Kind == "page" && query.Kind == "contract_query" && diff --git a/internal/compiler/validate_test.go b/internal/compiler/validate_test.go index 0e8cd7e..29630cf 100644 --- a/internal/compiler/validate_test.go +++ b/internal/compiler/validate_test.go @@ -3572,6 +3572,16 @@ func TestValidateManifestRejectsInvalidContractReferenceRoutes(t *testing.T) { viewBody: `
`, message: "without query, fragment, or params", }, + { + name: "trailing slash command action", + viewBody: ``, + message: "clean absolute path", + }, + { + name: "relative command action", + viewBody: ``, + message: "local absolute path", + }, { name: "unsupported command method", viewBody: ``, @@ -3835,6 +3845,26 @@ func TestValidateManifestRejectsRouteMethodConflicts(t *testing.T) { } }) + t.Run("identical command route references are allowed", func(t *testing.T) { + app := appFixture{ + Pages: []gwdkir.Page{{ + ID: "patients", + Route: "/patients", + Blocks: gwdkir.Blocks{ + View: true, + ViewBody: `