Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
with:
go-version: 1.25.x
cache: true
- run: make lint
- run: make test-race
- run: make proto-lint
- run: make check-generate
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
/bin/
*.exe
*.exe~
*.dll
Expand Down
104 changes: 104 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
version: "2"

run:
timeout: 5m
modules-download-mode: readonly
tests: true

linters:
default: none
enable:
- bodyclose
- copyloopvar
- errcheck
- errorlint
- govet
- grouper
- ineffassign
- misspell
- nolintlint
- revive
- staticcheck
- unused
- wsl_v5
settings:
grouper:
# A single type may remain standalone; related types share one declaration.
type-require-single-type: true
nolintlint:
require-explanation: true
require-specific: true
allow-unused: false
revive:
severity: error
confidence: 0.8
enable-default-rules: false
rules:
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: empty-block
- name: error-naming
- name: error-return
- name: error-strings
- name: errorf
- name: exported
- name: if-return
- name: increment-decrement
- name: indent-error-flow
- name: package-comments
- name: range
- name: receiver-naming
- name: superfluous-else
- name: time-naming
- name: unexported-return
- name: unreachable-code
- name: unused-parameter
- name: var-declaration
- name: var-naming
wsl_v5:
# Enforce the control-flow spacing rules in AGENTS.md.
default: none
enable:
- if
- err
- return
- branch
- after-block
- leading-whitespace
- trailing-whitespace
allow-first-in-block: false
allow-whole-block: false
branch-max-lines: 0
case-max-lines: 0
cuddle-max-statements: 1
exclusions:
generated: strict
presets: []
rules:
# Wire is the product name; retain it in existing error messages.
- linters:
- staticcheck
text: '^ST1005: error strings should not be capitalized$'
source: '(errors\.New|fmt\.Errorf)\("Wire '
paths:
- ^gen/
- ^vendor/

formatters:
enable:
- gofmt
- goimports
settings:
goimports:
local-prefixes:
- github.com/MontFerret
exclusions:
generated: strict
paths:
- ^gen/
- ^vendor/

issues:
max-issues-per-linter: 0
max-same-issues: 0
15 changes: 13 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ Canonical repository validation is:
```sh
make fmt
make check-fmt
make lint
make generate
make check-generate
make proto-lint
Expand All @@ -261,6 +262,16 @@ make test-race
make build
```

`make fmt`, `make check-fmt`, and `make lint` automatically install the pinned
golangci-lint binary when absent; `make install-lint` is optional. First use needs
download access, curl, and a POSIX shell. Lint covers handwritten code and tests,
including exported declaration comments, error handling, and the type and
spacing rules above. Formatting uses gofmt and goimports; generated and vendor
code are excluded. Keep suppressions specific, explained, and effective, and
preserve intentional error and panic identities. See the
[development instructions](README.md#development) for the rule list and
suppression guidance.

Use the relevant subset for narrow iteration, then broaden according to risk.
`make generate` is required when generator inputs change.

Expand Down Expand Up @@ -325,8 +336,8 @@ Do not use self-review to justify speculative redesign or unrelated cleanup.
## CI and documentation synchronization

CI uses the Makefile's canonical targets on Linux, macOS, and Windows. Linux
also runs race detection, protobuf linting, generation consistency checks, and
pull-request Buf breaking checks against the fetched base branch. Keep CI
also runs Go lint, race detection, protobuf linting, generation consistency checks,
and pull-request Buf breaking checks against the fetched base branch. Keep CI
orchestration in the workflow and command composition in the Makefile.

Documentation is part of implementation. Keep detailed architecture, protocol
Expand Down
30 changes: 25 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
BUF = go run github.com/bufbuild/buf/cmd/buf@v1.72.0
BUF_BREAKING_AGAINST ?= .git#branch=main
DIR_BIN = ./bin
GOLANGCI_LINT_VERSION = v2.13.2
GOLANGCI_LINT_DIR = $(DIR_BIN)/tools/golangci-lint/$(GOLANGCI_LINT_VERSION)
GOLANGCI_LINT_SUFFIX := $(if $(filter windows,$(shell go env GOHOSTOS)),.exe)
GOLANGCI_LINT = $(GOLANGCI_LINT_DIR)/golangci-lint$(GOLANGCI_LINT_SUFFIX)

.PHONY: build check-fmt check-generate check-tidy fmt generate proto-breaking proto-lint test test-race vet
.PHONY: build check-fmt check-generate check-tidy fmt generate install-lint lint proto-breaking proto-lint test test-race vet

build:
go build ./...

fmt:
go fmt ./...
install-lint: $(GOLANGCI_LINT)

check-fmt:
test -z "$$(gofmt -l $$(find . -type f -name '*.go' -not -path './.git/*'))"
$(GOLANGCI_LINT):
@set -eu; \
lint_installer=$$(mktemp); \
trap 'rm -f "$$lint_installer"' 0; \
curl --fail --silent --show-error --location \
"https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh" \
--output "$$lint_installer"; \
sh "$$lint_installer" -b "$(GOLANGCI_LINT_DIR)" "$(GOLANGCI_LINT_VERSION)"

fmt: $(GOLANGCI_LINT)
$(GOLANGCI_LINT) fmt ./...

check-fmt: $(GOLANGCI_LINT)
$(GOLANGCI_LINT) fmt --diff ./...

lint: $(GOLANGCI_LINT)
$(GOLANGCI_LINT) config verify && \
$(GOLANGCI_LINT) run ./...

generate:
$(BUF) generate
Expand Down
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ does not synthesize intermediate output from logs.
```sh
make fmt # format handwritten Go
make check-fmt # verify formatting without changing files
make lint # verify configuration and lint the complete Go baseline
make generate # regenerate checked-in Go/gRPC bindings
make check-generate # fail when generation changes the checkout
make proto-lint # Buf STANDARD lint
Expand All @@ -194,13 +195,41 @@ make test-race
make build
```

The Makefile pins golangci-lint and installs its official, checksum-verified
release under ignored `bin/tools/golangci-lint/<version>/`. `make fmt`,
`make check-fmt`, and `make lint` install it automatically when the selected
version's executable is absent and reuse it afterward. Explicit installation
with `make install-lint` is optional. First use requires download access, `curl`,
and a POSIX shell (such as Git Bash on Windows). Tool selection uses the host
platform even when Go cross-compilation variables are set. `make build` remains
a compilation-only target.

[The lint configuration](.golangci.yml) enables correctness, error handling,
resource cleanup, spelling, API documentation and naming, grouped type
declarations, and control-flow spacing checks. Its explicit linter list is
`errcheck`, `govet`, `ineffassign`, `staticcheck`, `unused`, `bodyclose`,
`errorlint`, `copyloopvar`, `nolintlint`, `revive`, `grouper`, `misspell`,
and `wsl_v5`. Formatting uses `gofmt` and `goimports`, with
`github.com/MontFerret` imports grouped together. Tests are covered; generated
code and vendor directories are excluded from lint and formatting. Linting is
read-only and analyzes the full baseline.

Fix findings at their source. When a check conflicts with an intentional
contract, use a narrow directive such as
`//nolint:errorlint // Verify the original error is returned unchanged.`
Suppressions must name the linter, explain the reason, and suppress a real
finding. Preserve exact-error and panic-identity assertions. A configuration
exception covers only capitalization warnings for error literals beginning
with the proper name "Wire"; other Staticcheck checks remain enabled.
Architectural and ownership rules still require review.

The [Universal API integration suite](test/integration/README.md) exercises the
public client/server boundary over real gRPC using an in-memory `bufconn`
transport and hosted API spies. Run it independently with
`go test ./test/integration/...` or `go test -race ./test/integration/...`.
Package-local tests retain component, conversion, and low-level protocol coverage.

CI invokes these Make targets on Linux, macOS, and Windows; Linux additionally
runs the race detector, Buf lint, checked generation, and pull-request breaking
checks against the fetched base branch. The integration suite is included in
the existing `./...` targets without build tags or extra services.
runs Go lint, the race detector, Buf lint, checked generation, and pull-request
breaking checks against the fetched base branch. The integration suite is included
in the existing `./...` targets without build tags or extra services.
1 change: 1 addition & 0 deletions client/allocation_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (e *allocationError) Unwrap() error {

func allocationRPCError(err error) error {
decoded := decodeError(err)

var rejection *Error
if errors.As(decoded, &rejection) && rejection.Category != 0 {
// Structured Wire failures describe a rejected creation. Its owner rolls
Expand Down
7 changes: 6 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
"io"
"sync"

"google.golang.org/grpc"

wirev1 "github.com/MontFerret/wire/gen/ferret/wire/v1"
"github.com/MontFerret/wire/pkg/failure"
"google.golang.org/grpc"
)

type (
Expand Down Expand Up @@ -47,6 +48,7 @@ func newConnection(ctx context.Context, connection grpc.ClientConnInterface) (*c

runtimeClient := wirev1.NewRuntimeServiceClient(connection)
streamCtx, streamCancel := context.WithCancel(context.WithoutCancel(ctx))

stream, err := runtimeClient.Connect(streamCtx, &wirev1.ConnectRequest{})
if err != nil {
streamCancel()
Expand Down Expand Up @@ -163,6 +165,7 @@ func (c *connectionHandle) checkOpen() error {
c.closeMu.Lock()
closing := c.closing
c.closeMu.Unlock()

if closing {
return ErrClosed
}
Expand All @@ -172,6 +175,7 @@ func (c *connectionHandle) checkOpen() error {
c.streamMu.Lock()
err := c.streamErr
c.streamMu.Unlock()

if err != nil {
return err
}
Expand All @@ -190,6 +194,7 @@ func (c *connectionHandle) closeResult(ctx context.Context) (bool, error) {
c.closeMu.Lock()
closing := c.closing
c.closeMu.Unlock()

if !closing {
return false, nil
}
Expand Down
Loading
Loading