validrb 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fb818549e6bca37a89242aee2e72b9f74c32cbafa3db4bb6cbaad060b93fc426
4
+ data.tar.gz: 0d7debd6603cbf1afb0bf664e1a3252de6c270bd2844ea8f2cd1406b198e4008
5
+ SHA512:
6
+ metadata.gz: 33da9e6eefcc7c7b4ab78d8785c7b12b0216f8f0677b4d7120ebec5056ba1b6a42030afa3c13618c03d4e22e47538da085bc0783db7973698dcec6d2b44fea20
7
+ data.tar.gz: 2845fbd1a754e91509692d671d5ff050e738cc8799625d34028acc77cf47a041861f80556b23cefb05206c58b56350c62643d540b2ad6e51d5cc2644fea1ce16
data/CHANGELOG.md ADDED
@@ -0,0 +1,99 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.5.0] - 2024-01-15
9
+
10
+ ### Added
11
+ - **OpenAPI 3.0 Generation** - Generate complete OpenAPI specs from schemas
12
+ - **OpenAPI Import** - Create Validrb schemas from OpenAPI/JSON Schema definitions
13
+ - `Validrb::OpenAPI::Generator` for building OpenAPI documents
14
+ - `Validrb::OpenAPI::PathBuilder` for defining API endpoints
15
+ - `Validrb::OpenAPI::Importer` for importing external schemas
16
+ - `Schema#to_openapi` method for single schema export
17
+ - Support for servers, paths, and component schemas in OpenAPI output
18
+ - JSON and YAML export formats
19
+
20
+ ### Production Readiness
21
+ - Comprehensive README documentation
22
+ - CHANGELOG following Keep a Changelog format
23
+ - GitHub Actions CI workflow for Ruby 3.0-3.3
24
+ - Updated gemspec with full metadata
25
+
26
+ ## [0.4.0] - 2024-01-15
27
+
28
+ ### Added
29
+ - **Literal types** - Exact value matching with `literal:` option
30
+ - **Refinements** - Custom validation predicates with `refine:` option
31
+ - **Validation context** - Pass request-level data through validation pipeline
32
+ - **Schema introspection** - `field_names`, `required_fields`, `optional_fields`, `to_schema_hash`
33
+ - **JSON Schema generation** - `to_json_schema` method for JSON Schema Draft-07 output
34
+ - **Custom type API** - `Validrb.define_type` DSL for creating custom types
35
+ - **Discriminated unions** - Schema selection based on discriminator field
36
+ - **Serialization** - `dump` and `to_json` methods for converting data to primitives
37
+ - Context support in transforms, preprocessors, refinements, and validators
38
+
39
+ ### Changed
40
+ - Field validation now accepts optional `context:` parameter
41
+ - Schema `safe_parse` and `parse` now accept optional `context:` parameter
42
+ - Validators can now receive context as second argument
43
+
44
+ ## [0.3.0] - 2024-01-14
45
+
46
+ ### Added
47
+ - **Preprocessing** - `preprocess:` option for transforming input before validation
48
+ - **Conditional validation** - `when:` and `unless:` options for conditional field validation
49
+ - **Union types** - Accept multiple types with `union:` option
50
+ - **Coercion modes** - Disable coercion per-field with `coerce: false`
51
+ - **I18n support** - Internationalized error messages with locale switching
52
+ - `Validrb::I18n` module with `add_translations`, `locale`, and `reset!`
53
+
54
+ ### Changed
55
+ - Validation pipeline now: preprocess -> coerce -> constraints -> transform
56
+ - Conditional fields are skipped when condition is false (treated as optional)
57
+
58
+ ## [0.2.0] - 2024-01-13
59
+
60
+ ### Added
61
+ - **Custom validators** - Cross-field validation with `validate` blocks
62
+ - **Custom error messages** - `message:` option for fields
63
+ - **Date/DateTime/Time types** - Full temporal type support with coercion
64
+ - **Decimal type** - BigDecimal support for precise numeric values
65
+ - **Schema composition** - `extend`, `merge`, `pick`, `omit`, `partial` methods
66
+ - **Unknown key handling** - `strict:` and `passthrough:` schema options
67
+ - **Transforms** - `transform:` option for post-validation transformation
68
+ - **Nullable fields** - `nullable:` option (distinct from `optional:`)
69
+ - ValidatorContext class for custom validators with `error` and `base_error` methods
70
+
71
+ ### Changed
72
+ - Schema now supports composition methods for building derived schemas
73
+ - Error paths now support nested structures correctly
74
+
75
+ ## [0.1.0] - 2024-01-12
76
+
77
+ ### Added
78
+ - Initial release
79
+ - **Core types** - string, integer, float, boolean, array, object
80
+ - **Type coercion** - Automatic conversion between compatible types
81
+ - **Constraints** - min, max, length, format, enum
82
+ - **Schema DSL** - `Validrb.schema` with `field` declarations
83
+ - **Parsing methods** - `parse` (raises) and `safe_parse` (returns Result)
84
+ - **Result types** - `Success` and `Failure` with data/errors
85
+ - **Error tracking** - Path-tracked errors with ErrorCollection
86
+ - **Named formats** - email, url, uuid, phone, alphanumeric, alpha, numeric, hex, slug
87
+ - **Nested validation** - Objects and arrays with item/schema validation
88
+ - **Field options** - optional, default, nullable
89
+ - Zero runtime dependencies
90
+
91
+ ### Notes
92
+ - Requires Ruby >= 3.0
93
+ - Inspired by Pydantic (Python) and Zod (TypeScript)
94
+
95
+ [0.5.0]: https://github.com/validrb/validrb/compare/v0.4.0...v0.5.0
96
+ [0.4.0]: https://github.com/validrb/validrb/compare/v0.3.0...v0.4.0
97
+ [0.3.0]: https://github.com/validrb/validrb/compare/v0.2.0...v0.3.0
98
+ [0.2.0]: https://github.com/validrb/validrb/compare/v0.1.0...v0.2.0
99
+ [0.1.0]: https://github.com/validrb/validrb/releases/tag/v0.1.0
data/CLAUDE.md ADDED
@@ -0,0 +1,434 @@
1
+ # Validrb - Development Context
2
+
3
+ ## Project Overview
4
+
5
+ Validrb is a Ruby schema validation library with type coercion, inspired by Pydantic (Python) and Zod (TypeScript). It provides a clean DSL for defining data schemas with automatic type coercion, constraint validation, and detailed error reporting.
6
+
7
+ ## Current Status: Phase 5 Complete
8
+
9
+ All core functionality implemented and tested (719 tests passing).
10
+
11
+ ## Architecture
12
+
13
+ ```
14
+ Validrb.schema { ... }
15
+
16
+
17
+ Schema (DSL, parse/safe_parse, composition, introspection)
18
+
19
+ ├── Validators (custom cross-field validation + context)
20
+ ├── Serialization (dump/to_json)
21
+
22
+
23
+ Field (preprocess → type → constraints → refinements → transform)
24
+
25
+ ├── Conditional (when:/unless: with context)
26
+ ├── Coercion modes (coerce: true/false)
27
+ ├── Literal types (exact value matching)
28
+ ├── Refinements (custom predicates)
29
+
30
+ ├──────────────┬──────────────┐
31
+ ▼ ▼ ▼
32
+ Types Constraints Context
33
+ (coerce→validate) (min/max/enum/format) (request context)
34
+
35
+ ├── Union types
36
+ ├── Discriminated unions
37
+ ├── Custom types
38
+
39
+
40
+ Result (Success/Failure + serialization)
41
+ Errors (path-tracked, I18n)
42
+ ```
43
+
44
+ ## File Structure
45
+
46
+ ```
47
+ lib/
48
+ ├── validrb.rb # Main entry point
49
+ └── validrb/
50
+ ├── version.rb # VERSION = "0.5.0"
51
+ ├── i18n.rb # I18n support for error messages
52
+ ├── errors.rb # Error, ErrorCollection, ValidationError
53
+ ├── result.rb # Success, Failure result types
54
+ ├── context.rb # Validation context
55
+ ├── field.rb # Field definition with all options
56
+ ├── schema.rb # Schema class with DSL Builder + composition
57
+ ├── custom_type.rb # Custom type DSL
58
+ ├── introspection.rb # Schema/field introspection
59
+ ├── serializer.rb # Serialization to primitives/JSON
60
+ ├── openapi.rb # OpenAPI 3.0 generation and import
61
+ ├── types/
62
+ │ ├── base.rb # Base type class + registry
63
+ │ ├── string.rb # String type
64
+ │ ├── integer.rb # Integer type
65
+ │ ├── float.rb # Float type
66
+ │ ├── boolean.rb # Boolean type
67
+ │ ├── array.rb # Array type with item validation
68
+ │ ├── object.rb # Object type for nested schemas
69
+ │ ├── date.rb # Date type
70
+ │ ├── datetime.rb # DateTime type
71
+ │ ├── time.rb # Time type
72
+ │ ├── decimal.rb # Decimal type (BigDecimal)
73
+ │ ├── union.rb # Union type (multiple types)
74
+ │ ├── literal.rb # Literal type (exact values)
75
+ │ └── discriminated_union.rb # Discriminated union type
76
+ └── constraints/
77
+ ├── base.rb # Base constraint class + registry
78
+ ├── min.rb # Minimum value/length
79
+ ├── max.rb # Maximum value/length
80
+ ├── length.rb # Exact/range/min/max length
81
+ ├── format.rb # Regex or named formats
82
+ └── enum.rb # Value in allowed list
83
+
84
+ spec/
85
+ ├── spec_helper.rb
86
+ ├── validrb_spec.rb
87
+ ├── validrb/
88
+ │ ├── errors_spec.rb
89
+ │ ├── result_spec.rb
90
+ │ ├── field_spec.rb
91
+ │ ├── schema_spec.rb
92
+ │ ├── i18n_spec.rb
93
+ │ ├── context_spec.rb
94
+ │ ├── custom_type_spec.rb
95
+ │ ├── serializer_spec.rb
96
+ │ ├── introspection_spec.rb
97
+ │ ├── openapi_spec.rb
98
+ │ ├── types/*.rb
99
+ │ └── constraints/*.rb
100
+ └── integration/
101
+ ├── basic_schema_spec.rb
102
+ ├── nested_schema_spec.rb
103
+ ├── phase2_features_spec.rb
104
+ ├── phase3_features_spec.rb
105
+ ├── phase4_features_spec.rb
106
+ └── phase4_edge_cases_spec.rb
107
+ ```
108
+
109
+ ## API Reference
110
+
111
+ ### Schema Definition
112
+
113
+ ```ruby
114
+ schema = Validrb.schema do
115
+ # Basic fields
116
+ field :name, :string
117
+ field :age, :integer, optional: true
118
+ field :role, :string, default: "user"
119
+
120
+ # Constraints
121
+ field :email, :string, format: :email
122
+ field :bio, :string, min: 10, max: 500
123
+ field :status, :string, enum: %w[active inactive]
124
+
125
+ # Preprocessing (runs BEFORE validation)
126
+ field :username, :string, preprocess: ->(v) { v.strip.downcase }
127
+
128
+ # Transform (runs AFTER validation)
129
+ field :tags, :string, transform: ->(v) { v.split(",") }
130
+
131
+ # Nullable (accepts nil)
132
+ field :deleted_at, :datetime, nullable: true
133
+
134
+ # Union types (accepts multiple types)
135
+ field :id, :string, union: [:integer, :string]
136
+
137
+ # Literal types (exact values only)
138
+ field :priority, :integer, literal: [1, 2, 3]
139
+
140
+ # Refinements (custom predicates)
141
+ field :password, :string,
142
+ refine: [
143
+ { check: ->(v) { v.length >= 8 }, message: "must be 8+ chars" },
144
+ { check: ->(v) { v.match?(/\d/) }, message: "must contain digit" }
145
+ ]
146
+
147
+ # Context-aware refinement
148
+ field :amount, :decimal,
149
+ refine: ->(v, ctx) { ctx.nil? || v <= ctx[:max_amount] }
150
+
151
+ # Disable coercion
152
+ field :count, :integer, coerce: false
153
+
154
+ # Conditional validation (supports context)
155
+ field :company, :string, when: ->(d, ctx) { d[:account_type] == "business" }
156
+ field :personal_id, :string, unless: :is_company
157
+
158
+ # Custom error message
159
+ field :note, :string, min: 8, message: "must be at least 8 characters"
160
+
161
+ # Nested schema
162
+ field :address, :object, schema: AddressSchema
163
+
164
+ # Typed arrays
165
+ field :scores, :array, of: :integer
166
+
167
+ # Discriminated union
168
+ field :payment, :discriminated_union,
169
+ discriminator: :method,
170
+ mapping: { "card" => CardSchema, "paypal" => PaypalSchema }
171
+
172
+ # Custom validators (support context)
173
+ validate do |data, ctx|
174
+ if ctx && ctx[:restricted] && data[:amount] > 100
175
+ error(:amount, "exceeds limit in restricted mode")
176
+ end
177
+ end
178
+ end
179
+ ```
180
+
181
+ ### Schema Options
182
+
183
+ ```ruby
184
+ # Strict mode - reject unknown keys
185
+ Validrb.schema(strict: true) { ... }
186
+
187
+ # Passthrough mode - keep unknown keys
188
+ Validrb.schema(passthrough: true) { ... }
189
+ ```
190
+
191
+ ### Field Options Reference
192
+
193
+ | Option | Type | Description |
194
+ |--------|------|-------------|
195
+ | `optional` | Boolean | Field can be missing (default: false) |
196
+ | `nullable` | Boolean | Field accepts nil value (default: false) |
197
+ | `default` | Any/Proc | Default value when missing |
198
+ | `message` | String | Custom error message |
199
+ | `preprocess` | Proc | Transform input BEFORE validation |
200
+ | `transform` | Proc | Transform value AFTER validation |
201
+ | `coerce` | Boolean | Enable type coercion (default: true) |
202
+ | `when` | Proc/Symbol | Only validate if condition is true |
203
+ | `unless` | Proc/Symbol | Only validate if condition is false |
204
+ | `union` | Array | Accept any of these types |
205
+ | `literal` | Array | Accept only these exact values |
206
+ | `refine` | Proc/Array | Custom validation predicates |
207
+ | `min` | Numeric | Minimum value/length |
208
+ | `max` | Numeric | Maximum value/length |
209
+ | `length` | Int/Range/Hash | Length constraint |
210
+ | `format` | Symbol/Regexp | Format validation |
211
+ | `enum` | Array | Allowed values |
212
+ | `of` | Symbol | Item type for arrays |
213
+ | `schema` | Schema | Nested schema for objects |
214
+ | `discriminator` | Symbol | Field for discriminated union |
215
+ | `mapping` | Hash | Schemas for discriminated union |
216
+
217
+ ### Type Coercion Rules
218
+
219
+ | Type | Accepts | Coerces From |
220
+ |------|---------|--------------|
221
+ | `:string` | String | Symbol, Numeric |
222
+ | `:integer` | Integer | String, Float (whole) |
223
+ | `:float` | Float | String, Integer |
224
+ | `:boolean` | true/false | "true"/"false", "yes"/"no", 1/0, etc. |
225
+ | `:array` | Array | (validates items with `of:`) |
226
+ | `:object` | Hash | (validates with `schema:`) |
227
+ | `:date` | Date | ISO8601 String, Time, DateTime, timestamp |
228
+ | `:datetime` | DateTime | ISO8601 String, Time, Date, timestamp |
229
+ | `:time` | Time | ISO8601 String, DateTime, Date, timestamp |
230
+ | `:decimal` | BigDecimal | String, Integer, Float, Rational |
231
+ | `:union` | Any matching | Tries each type in order |
232
+ | `:literal` | Exact value | No coercion, exact match only |
233
+ | `:discriminated_union` | Object | Selects schema by discriminator field |
234
+
235
+ ### Named Formats
236
+
237
+ `:email`, `:url`, `:uuid`, `:phone`, `:alphanumeric`, `:alpha`, `:numeric`, `:hex`, `:slug`
238
+
239
+ ### Schema Composition
240
+
241
+ ```ruby
242
+ # Extend with additional fields
243
+ UserSchema = BaseSchema.extend { field :name, :string }
244
+
245
+ # Pick specific fields
246
+ PublicSchema = FullSchema.pick(:id, :name)
247
+
248
+ # Omit fields
249
+ SafeSchema = FullSchema.omit(:password)
250
+
251
+ # Merge schemas
252
+ MergedSchema = Schema1.merge(Schema2)
253
+
254
+ # Make all fields optional
255
+ UpdateSchema = CreateSchema.partial
256
+ ```
257
+
258
+ ### I18n Configuration
259
+
260
+ ```ruby
261
+ # Add custom translations
262
+ Validrb::I18n.add_translations(:en, required: "cannot be blank")
263
+
264
+ # Change locale
265
+ Validrb::I18n.locale = :es
266
+
267
+ # Add translations for other locales
268
+ Validrb::I18n.add_translations(:es, required: "es requerido")
269
+
270
+ # Use Rails I18n backend
271
+ Validrb::I18n.backend = :rails
272
+ ```
273
+
274
+ ### Validation Context
275
+
276
+ ```ruby
277
+ # Create a context
278
+ ctx = Validrb.context(user_id: 123, is_admin: true, max_amount: 1000)
279
+
280
+ # Pass context to parse
281
+ result = schema.safe_parse(data, context: ctx)
282
+
283
+ # Context is available in refinements, conditions, transforms, and validators
284
+ field :amount, :decimal, refine: ->(v, ctx) { v <= ctx[:max_amount] }
285
+ field :admin_only, :string, when: ->(data, ctx) { ctx[:is_admin] }
286
+ ```
287
+
288
+ ### Custom Types
289
+
290
+ ```ruby
291
+ # Define a custom type
292
+ Validrb.define_type(:money) do
293
+ coerce { |v| BigDecimal(v.to_s.gsub(/[$,]/, "")) }
294
+ validate { |v| v >= 0 }
295
+ error_message { "must be a valid money amount" }
296
+ end
297
+
298
+ # Use in schemas
299
+ field :price, :money
300
+ ```
301
+
302
+ ### Schema Introspection
303
+
304
+ ```ruby
305
+ # Field inspection
306
+ schema.field_names # => [:id, :name, :email]
307
+ schema.required_fields # => [:id, :name]
308
+ schema.optional_fields # => [:age]
309
+ schema.fields_with_defaults # => [:role]
310
+ schema.conditional_fields # => [:company]
311
+
312
+ # Field details
313
+ field = schema.field(:name)
314
+ field.constraint_values # => { min: 1, max: 100 }
315
+ field.has_constraint?(Validrb::Constraints::Min) # => true
316
+
317
+ # Generate JSON Schema
318
+ schema.to_json_schema # => { "$schema": "...", "type": "object", ... }
319
+ ```
320
+
321
+ ### Serialization
322
+
323
+ ```ruby
324
+ # Parse and serialize to hash with primitives
325
+ result = schema.safe_parse(data)
326
+ result.dump # => { "name" => "John", "date" => "2024-01-15" }
327
+ result.dump(format: :json) # => '{"name":"John","date":"2024-01-15"}'
328
+ result.to_json # Same as dump(format: :json)
329
+
330
+ # Schema-level serialization
331
+ schema.dump(data) # Parse + serialize (raises on error)
332
+ schema.safe_dump(data) # Parse + serialize (returns Result)
333
+ ```
334
+
335
+ ### Validation Flow
336
+
337
+ ```
338
+ Input Value
339
+
340
+
341
+ ┌─────────────────┐
342
+ │ Conditional? │──No──┐
343
+ │ (when:/unless:) │ │
344
+ └────────┬────────┘ │
345
+ │Yes │
346
+ ▼ │
347
+ ┌─────────────────┐ │
348
+ │ Should validate?│──No──┼──► Skip (return nil)
349
+ └────────┬────────┘ │
350
+ │Yes │
351
+ ▼ │
352
+ ┌─────────────────┐ │
353
+ │ Preprocess │◄─────┘
354
+ │ (before coerce) │
355
+ └────────┬────────┘
356
+
357
+
358
+ ┌─────────────────┐
359
+ │ Type Coercion │──► coerce: false? → Type Check Only
360
+ │ (if enabled) │
361
+ └────────┬────────┘
362
+
363
+
364
+ ┌─────────────────┐
365
+ │ Constraints │
366
+ │ (min/max/etc) │
367
+ └────────┬────────┘
368
+
369
+
370
+ ┌─────────────────┐
371
+ │ Transform │
372
+ │ (after valid) │
373
+ └────────┬────────┘
374
+
375
+
376
+ Output Value
377
+ ```
378
+
379
+ ## Running Tests
380
+
381
+ ```bash
382
+ bundle install
383
+ bundle exec rspec # Run all tests
384
+ bundle exec rspec --format doc # Verbose output
385
+ ```
386
+
387
+ ## Version History
388
+
389
+ ### Phase 1 (v0.1.0)
390
+ - Basic types (string, integer, float, boolean, array, object)
391
+ - Constraints (min, max, length, format, enum)
392
+ - Schema DSL, type coercion, nested validation
393
+ - Error path tracking, parse/safe_parse
394
+
395
+ ### Phase 2 (v0.2.0)
396
+ - Custom validators (cross-field validation)
397
+ - Custom error messages
398
+ - Date/DateTime/Time types
399
+ - Decimal type (BigDecimal)
400
+ - Schema composition (extend, merge, pick, omit, partial)
401
+ - Unknown keys handling (strict/passthrough)
402
+ - Transforms (post-validation)
403
+ - Nullable fields
404
+
405
+ ### Phase 3 (v0.3.0)
406
+ - Preprocessing (pre-validation transformation)
407
+ - Conditional validation (when:/unless:)
408
+ - Union types (multiple type acceptance)
409
+ - Coercion modes (disable per-field)
410
+ - I18n support (internationalized errors)
411
+
412
+ ### Phase 4 (v0.4.0)
413
+ - Literal types (exact value matching)
414
+ - Refinements (custom validation predicates)
415
+ - Validation context (request-level data)
416
+ - Schema introspection (field inspection, JSON Schema generation)
417
+ - Custom type API (define_type DSL)
418
+ - Discriminated unions (type selection by discriminator)
419
+ - Serialization (dump to primitives/JSON)
420
+
421
+ ## Future Enhancements
422
+
423
+ - [ ] Async validators (database checks)
424
+ - [ ] Rails integration (ActiveModel compatibility)
425
+ - [ ] OpenAPI schema generation
426
+ - [ ] Dependent field validation DSL
427
+
428
+ ## Code Conventions
429
+
430
+ - All files use `# frozen_string_literal: true`
431
+ - Zero runtime dependencies (bigdecimal is stdlib)
432
+ - Ruby >= 3.0 required
433
+ - RSpec for testing
434
+ - Objects are frozen/immutable after creation
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Validrb Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.