-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy path.cursorrules
412 lines (319 loc) · 9.77 KB
/
.cursorrules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
## Overview
This document outlines the standards and best practices for developing an Elixir library. These guidelines ensure the library remains reliable, maintainable, secure, and easy to integrate into other Elixir applications.
This is a pure Elixir library intended to be used as a dependency in other Elixir applications. It is **not** a Phoenix or Nerves project. Instead, it focuses on providing functional building blocks using idiomatic Elixir and OTP constructs.
## Core Principles
- Write clean, composable, and testable code
- Adhere to functional programming principles—avoid OOP patterns
- Maintain clear boundaries between modules and domains
- Ensure code is robust, secure, and easy to reason about
- Provide rich documentation and helpful logging
- Create libraries that integrate seamlessly into any Elixir application
## Project Structure
### Directory Layout
```
.
├── lib/
│ ├── your_library/
│ │ ├── core/ # Core functionality and behaviors
│ │ ├── components/ # Main component modules
│ │ ├── otp/ # OTP components (supervisors, workers)
│ │ ├── utils/ # Utility functions and helpers
│ │ └── types/ # Custom types and specs
│ └── your_library.ex # Main entry point
├── test/
│ ├── your_library/
│ │ ├── core/ # Tests mirroring lib structure
│ │ ├── components/
│ │ └── otp/
│ ├── support/ # Test helpers and shared fixtures
│ └── test_helper.exs
├── mix.exs
└── mix.lock
```
### Structural Guidelines
- **Data First**: Define data structures and types before implementing operations on them
- **Module Organization**: Group modules by domain or functionality
- **Test Mirroring**: Tests should mirror the directory structure
- **Minimal Dependencies**: Avoid circular dependencies between modules
- **Clear Boundaries**: Each module should have a single responsibility
## Code Organization
### Data Structure Definition
1. Start with pure data structures using structs:
```elixir
defmodule YourLibrary.Types.Task do
use TypedStruct
typedstruct do
field :id, String.t()
field :name, String.t()
field :status, :pending | :running | :completed
field :created_at, DateTime.t()
end
@type validation_error ::
:invalid_name |
:invalid_status |
{:invalid_date, String.t()}
@spec validate(t()) :: :ok | {:error, validation_error()}
def validate(%__MODULE__{} = task) do
# Validation logic
end
end
```
2. Then define modules that operate on these structures:
```elixir
defmodule YourLibrary.Core.TaskOperations do
alias YourLibrary.Types.Task
@spec create_task(String.t()) :: {:ok, Task.t()} | {:error, Task.validation_error()}
def create_task(name) do
task = %Task{
id: generate_id(),
name: name,
status: :pending,
created_at: DateTime.utc_now()
}
case Task.validate(task) do
:ok -> {:ok, task}
{:error, _reason} = error -> error
end
end
end
```
3. Finally, implement process lifecycle modules:
```elixir
defmodule YourLibrary.OTP.TaskManager do
use GenServer
alias YourLibrary.Core.TaskOperations
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(opts) do
{:ok, %{tasks: %{}, opts: opts}}
end
# ... rest of GenServer implementation
end
```
### Function Heads and Guards
Use multiple function heads for clarity and control flow:
```elixir
defmodule YourLibrary.Core.DataProcessor do
# Match on specific values
def process(:empty), do: {:ok, []}
# Use guards for type checking
def process(data) when is_list(data) do
{:ok, Enum.map(data, &transform/1)}
end
# Pattern match on complex structures
def process(%{items: items, status: :ready} = data)
when is_list(items) and length(items) > 0 do
{:ok, process_items(items, data)}
end
# Catch-all case
def process(_invalid) do
{:error, :invalid_input}
end
# Private functions can also use guards
defp transform(item) when is_binary(item) do
String.upcase(item)
end
defp transform(item) when is_integer(item) do
Integer.to_string(item)
end
end
```
### Behaviors
Define behaviors to establish contracts between modules:
```elixir
defmodule YourLibrary.Core.Processor do
@doc """
Defines the contract for processing data.
"""
@callback process(data :: term()) ::
{:ok, term()} |
{:error, term()}
@doc """
Optional callback for data validation.
"""
@callback validate(input :: term()) ::
:ok |
{:error, term()}
@optional_callbacks validate: 1
# Can include default implementations
defmacro __using__(_opts) do
quote do
@behaviour YourLibrary.Core.Processor
# Default implementation for validate
@impl true
def validate(_input), do: :ok
# Allow overrides
defoverridable validate: 1
end
end
end
# Implementation example
defmodule YourLibrary.Core.StringProcessor do
use YourLibrary.Core.Processor
@impl true
def process(data) when is_binary(data) do
{:ok, String.upcase(data)}
end
@impl true
def validate(input) when is_binary(input) do
if String.valid?(input), do: :ok, else: {:error, :invalid_string}
end
end
```
## Code Quality Standards
### Formatting and Style
- Run `mix format` before committing code
- Use [Credo](https://hex.pm/packages/credo) for static analysis
- Follow standard Elixir style guide
### Documentation Requirements
- Add `@moduledoc` to every module
- Add `@doc` to every public function
- Include examples in documentation using `@example` when helpful
- Document not just what functions do, but why and how
- Generate documentation with [ExDoc](https://hex.pm/packages/ex_doc)
### Type Specifications
```elixir
@type my_type :: String.t() | atom()
@spec my_function(my_type) :: {:ok, term()} | {:error, term()}
def my_function(input) do
# Implementation
end
```
- Use `@type` and `@typep` for type definitions
- Add `@spec` for all public functions
- Keep type specs accurate and descriptive
- Use Dialyzer for static type checking
### Naming Conventions
- Use `snake_case` for functions and variables
- Use `PascalCase` for module names
- Choose descriptive names over terse ones
- Follow Elixir community conventions
## Functional Programming Guidelines
### Pure Functions
```elixir
# Prefer
def process_data(data) do
{:ok, transform(data)}
end
# Over
def process_data(data) do
save_to_disk(transform(data))
end
```
- Keep functions pure when possible
- Return tagged tuples (`{:ok, value}` or `{:error, reason}`)
- Avoid side effects in core logic
- Use pattern matching over conditional logic
### OTP Integration
```elixir
defmodule YourLibrary.Application do
use Application
def start(_type, _args) do
children = [
{YourLibrary.Server, []},
{YourLibrary.Cache, []}
]
opts = [strategy: :one_for_one, name: YourLibrary.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
- Structure OTP components for easy integration
- Use supervision trees appropriately
- Implement proper shutdown handling
- Follow OTP conventions and patterns
## Error Handling
### Error Pattern
```elixir
def complex_operation(input) do
with {:ok, data} <- validate(input),
{:ok, processed} <- process(data),
{:ok, result} <- format(processed) do
{:ok, result}
else
{:error, reason} -> {:error, reason}
end
end
```
- Use `with` statements for complex operations
- Return tagged tuples consistently
- Create custom error types when needed
- Avoid silent failures
### Logging
```elixir
require Logger
def important_function(arg) do
Logger.info("Processing #{inspect(arg)}")
# Implementation
rescue
e ->
Logger.error("Failed to process: #{inspect(e)}")
{:error, :processing_failed}
end
```
- Use appropriate log levels
- Include context in log messages
- Avoid logging sensitive data
- Configure logger in consuming applications
- Use Logger.info/error/warning/debug/error/critical
## Testing Standards
### Test Organization
```elixir
defmodule YourLibraryTest.Core.ProcessorTest do
use ExUnit.Case, async: true
alias YourLibrary.Core.StringProcessor
describe "process/1" do
test "processes valid string data" do
assert {:ok, "HELLO"} = StringProcessor.process("hello")
end
test "returns error for invalid input" do
assert {:error, _} = StringProcessor.process(123)
end
end
describe "validate/1" do
test "validates string input" do
assert :ok = StringProcessor.validate("valid")
assert {:error, :invalid_string} = StringProcessor.validate(<<255>>)
end
end
end
```
- Append `Test` to the module name
- Write comprehensive unit tests
- Use property-based testing where appropriate
- Maintain test readability
- Ensure tests are deterministic
### Test Coverage
- Aim for high test coverage
- Test edge cases and error conditions
- Include doctests for examples
- Use ExUnit tags for test organization
## Configuration
### Server Configuration
```elixir
# In config/config.exs of consuming application
config :your_library,
key: "value",
timeout: 5000
```
- Use application configuration
- Allow server configuration
- Provide sensible defaults
- Document all configuration options
## Versioning and Release
- Follow semantic versioning
- Maintain a CHANGELOG.md
- Tag releases in version control
- Update documentation with releases
## Security Considerations
- Handle sensitive data appropriately
- Validate all inputs
- Document security considerations
- Follow security best practices
## Performance
- Optimize only with benchmarks
- Document performance characteristics
- Consider resource usage
- Implement timeouts where appropriate