The Cotton project has been successfully refactored to separate CLI concerns from the core engine, resulting in a clean, modular architecture.
cotton/
├── main.go (CLI + startup logic)
├── runner.go (Test orchestration)
├── parser.go (Markdown parsing)
├── executor.go (HTTP execution)
└── evaluator.go (Expectation evaluation)
All files were in the main package with mixed responsibilities.
cotton/
├── main.go (Minimal entry point)
├── cli/
│ ├── app.go (CLI application & flag parsing)
│ └── formatter.go (Output presentation)
└── engine/
├── runner.go (Test orchestration)
├── parser.go (Markdown parsing)
├── executor.go (HTTP execution)
├── evaluator.go (Expectation evaluation)
└── errors.go (Typed errors: ParseError, RunError)
Two packages with clear responsibilities and well-defined boundaries.
- CLI Package: User interaction, input parsing, output formatting
- Engine Package: Business logic, completely independent of CLI
- Engine can be used as a library in other tools (web UI, API servers, IDE plugins)
- CLI can be replaced without touching engine logic
- New interfaces can be added without modifying existing code
- High Cohesion: Each file/package has a single, clear purpose
- Low Coupling: Engine has zero dependencies on CLI; only CLI depends on engine
- Engine logic can be tested without requiring CLI setup
- Output formatting can be tested independently
- Integration tests can work with the engine directly
-
app.go: Main CLI coordinator
- Flag parsing (-spec)
- Error handling
- Exit code management
- Orchestrates runner invocation
-
formatter.go: Output presenter
- Formats test results
- Handles console output
- Can be extended for different output formats (JSON, XML, etc.)
-
runner.go: Test orchestrator
- Executes setup specs
- Manages variable context
- Executes main request
- Evaluates expectations
- Executes teardown specs
- Detects cycles in setup/teardown chains
- Supports configurable HTTP timeout via
SetTimeout
-
parser.go: Markdown parser
- Reads spec files
- Extracts sections (setup, request, expectations, teardown)
- Supports both triple backticks and triple tildes for code fences; language annotations are allowed (e.g., ```http, ~~~json)
- Builds structured spec objects
- Uses precompiled regexes for links, variables, and expectations
-
executor.go: HTTP executor
- Substitutes variables in requests (strict: missing variables fail)
- Parses HTTP request format
- Executes requests with
http.Client.Timeout - Manages response bodies
-
evaluator.go: Expectation evaluator
- Validates JSON responses
- Compares values with operators
- Supports multiple comparison types
- Uses GJSON; JSON paths in spec must start with
$
The command-line interface remains simple, with additional safety flags:
./cotton [file|dir|glob ...] --timeout 30000 -f # fail-fast with 30s timeout
./cotton examples/*.spec.md -q # quiet mode (exit code only)--timeout <ms>: Valid values 1..60000; capped at 60000ms.- Missing template variables cause a parse-time failure.
- Any failed spec returns a non-zero exit code.
- Add Web UI: Import
enginepackage, add web handlers - Add REST API: Use
engineas service backend - Add Programmatic API: Users can import
enginedirectly - Multiple Output Formats: Create JSON/XML formatters alongside existing text formatter
- IDE Integration: Tools can embed engine without CLI overhead
- Testing: Unit test engine and CLI independently
See ARCHITECTURE.md for:
- Detailed package structure
- Design principles
- Component responsibilities
- Extension examples
- Import guidelines
✅ All functionality preserved
✅ Binary builds successfully
✅ Command-line interface compatible (adds --timeout, stricter validation)
✅ No breaking changes to behavior
- Preserve boundaries: Keep
engineindependent fromcli. - Add tests first: When changing parsing/evaluation rules, write tests in
engine/before refactoring. - Respect spec: Ensure JSON paths start with
$and code fences include both backticks and tildes. - Small steps: Prefer incremental refactors with clear responsibilities over large rewrites.
- Shared context: Maintain variable sharing across setup/test/teardown; avoid hidden side effects.