anyvali 0.0.1
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 +7 -0
- data/README.md +339 -0
- data/lib/anyvali/anyvali_document.rb +43 -0
- data/lib/anyvali/format/validators.rb +89 -0
- data/lib/anyvali/interchange/exporter.rb +40 -0
- data/lib/anyvali/interchange/importer.rb +201 -0
- data/lib/anyvali/issue_codes.rb +20 -0
- data/lib/anyvali/parse/coercion.rb +93 -0
- data/lib/anyvali/parse/coercion_config.rb +19 -0
- data/lib/anyvali/parse/defaults.rb +21 -0
- data/lib/anyvali/parse_result.rb +21 -0
- data/lib/anyvali/schema.rb +132 -0
- data/lib/anyvali/schemas/any_schema.rb +15 -0
- data/lib/anyvali/schemas/array_schema.rb +77 -0
- data/lib/anyvali/schemas/bool_schema.rb +22 -0
- data/lib/anyvali/schemas/enum_schema.rb +50 -0
- data/lib/anyvali/schemas/int_schema.rb +9 -0
- data/lib/anyvali/schemas/intersection_schema.rb +65 -0
- data/lib/anyvali/schemas/literal_schema.rb +52 -0
- data/lib/anyvali/schemas/never_schema.rb +20 -0
- data/lib/anyvali/schemas/null_schema.rb +22 -0
- data/lib/anyvali/schemas/nullable_schema.rb +40 -0
- data/lib/anyvali/schemas/number_schema.rb +214 -0
- data/lib/anyvali/schemas/object_schema.rb +150 -0
- data/lib/anyvali/schemas/optional_schema.rb +47 -0
- data/lib/anyvali/schemas/record_schema.rb +51 -0
- data/lib/anyvali/schemas/ref_schema.rb +58 -0
- data/lib/anyvali/schemas/string_schema.rb +113 -0
- data/lib/anyvali/schemas/tuple_schema.rb +72 -0
- data/lib/anyvali/schemas/union_schema.rb +50 -0
- data/lib/anyvali/schemas/unknown_schema.rb +15 -0
- data/lib/anyvali/validation_context.rb +11 -0
- data/lib/anyvali/validation_error.rb +13 -0
- data/lib/anyvali/validation_issue.rb +45 -0
- data/lib/anyvali.rb +176 -0
- metadata +107 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7c53ac42dbac09b5549467769f54d5597699feb351c0f77975cced4cb11bd569
|
|
4
|
+
data.tar.gz: c73e7af5ae8c388e30fe3e9040a93bbd212d0d1ad16a05761d8db2bb4fe0348f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e3f43ec3aa8ff4310fd866208fadb24997c0304fb0cfda21ed8967d614b5695abd345ed63fc47dbedb39acfa5b97b183f61357c042ef723007d96ab43fb8a2e4
|
|
7
|
+
data.tar.gz: dbf001ffb2e24ac3d881fe4066b2f675f44f66f47123b684ab8828b8adf441f76c7d5b8665e91fce3939493e06b62e58f2a99902fda0a33869904b3daf640b59
|
data/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.png" alt="AnyVali" width="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">AnyVali</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Native validation libraries for 10 languages, one portable schema model.</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://github.com/BetterCorp/AnyVali/actions/workflows/ci.yml"><img src="https://github.com/BetterCorp/AnyVali/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
13
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT" /></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@anyvali/js"><img src="https://img.shields.io/npm/v/%40anyvali%2Fjs.svg?label=npm" alt="npm" /></a>
|
|
15
|
+
<a href="https://pypi.org/project/anyvali/"><img src="https://img.shields.io/pypi/v/anyvali.svg?label=pypi" alt="PyPI" /></a>
|
|
16
|
+
<a href="https://crates.io/crates/anyvali"><img src="https://img.shields.io/crates/v/anyvali.svg?label=crates.io" alt="crates.io" /></a>
|
|
17
|
+
<a href="https://pkg.go.dev/github.com/BetterCorp/AnyVali/sdk/go"><img src="https://img.shields.io/badge/go-pkg.go.dev-blue.svg" alt="Go" /></a>
|
|
18
|
+
<a href="https://www.nuget.org/packages/AnyVali"><img src="https://img.shields.io/nuget/v/AnyVali.svg?label=nuget" alt="NuGet" /></a>
|
|
19
|
+
<a href="https://rubygems.org/gems/anyvali"><img src="https://img.shields.io/gem/v/anyvali.svg?label=gem" alt="Gem" /></a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<a href="https://anyvali.com">Website</a> ·
|
|
24
|
+
<a href="https://docs.anyvali.com">Docs</a> ·
|
|
25
|
+
<a href="https://github.com/BetterCorp/AnyVali/issues">Issues</a> ·
|
|
26
|
+
<a href="CONTRIBUTING.md">Contributing</a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
AnyVali lets you write validation schemas in your language, then share them across any of 10 supported runtimes via a portable JSON format. Think Zod, but for every language.
|
|
32
|
+
|
|
33
|
+
## Why AnyVali?
|
|
34
|
+
|
|
35
|
+
- **Write schemas natively** -- idiomatic APIs for each language, not a separate DSL
|
|
36
|
+
- **Share across languages** -- export to JSON, import in any other SDK
|
|
37
|
+
- **Safe numeric defaults** -- `number` = float64, `int` = int64 everywhere
|
|
38
|
+
- **Deterministic parsing** -- coerce, default, then validate, in that order
|
|
39
|
+
- **Conformance tested** -- shared test corpus ensures identical behavior across SDKs
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @anyvali/js # JavaScript / TypeScript
|
|
45
|
+
pip install anyvali # Python
|
|
46
|
+
go get github.com/BetterCorp/AnyVali/sdk/go # Go
|
|
47
|
+
cargo add anyvali # Rust
|
|
48
|
+
dotnet add package AnyVali # C#
|
|
49
|
+
composer require anyvali/anyvali # PHP
|
|
50
|
+
gem install anyvali # Ruby
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary>Java / Kotlin / C++</summary>
|
|
55
|
+
|
|
56
|
+
**Java (Maven)**
|
|
57
|
+
```xml
|
|
58
|
+
<dependency>
|
|
59
|
+
<groupId>com.anyvali</groupId>
|
|
60
|
+
<artifactId>anyvali</artifactId>
|
|
61
|
+
<version>0.0.1</version>
|
|
62
|
+
</dependency>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Kotlin (Gradle)**
|
|
66
|
+
```kotlin
|
|
67
|
+
implementation("com.anyvali:anyvali:0.0.1")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**C++ (CMake)**
|
|
71
|
+
```cmake
|
|
72
|
+
FetchContent_Declare(anyvali GIT_REPOSITORY https://github.com/BetterCorp/AnyVali)
|
|
73
|
+
FetchContent_MakeAvailable(anyvali)
|
|
74
|
+
target_link_libraries(your_target PRIVATE anyvali)
|
|
75
|
+
```
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
Define a schema, parse input, get structured errors or clean data.
|
|
81
|
+
|
|
82
|
+
<table>
|
|
83
|
+
<tr><th>JavaScript / TypeScript</th><th>Python</th></tr>
|
|
84
|
+
<tr>
|
|
85
|
+
<td>
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { string, int, object, array } from "@anyvali/js";
|
|
89
|
+
|
|
90
|
+
const User = object({
|
|
91
|
+
name: string().minLength(1),
|
|
92
|
+
email: string().format('email'),
|
|
93
|
+
age: int().min(0).optional(),
|
|
94
|
+
tags: array(string()).maxItems(5),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Throws on failure
|
|
98
|
+
const user = User.parse(input);
|
|
99
|
+
|
|
100
|
+
// Or get a result object
|
|
101
|
+
const result = User.safeParse(input);
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
console.log(result.issues);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
</td>
|
|
108
|
+
<td>
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import anyvali as v
|
|
112
|
+
|
|
113
|
+
User = v.object_({
|
|
114
|
+
"name": v.string().min_length(1),
|
|
115
|
+
"email": v.string().format("email"),
|
|
116
|
+
"age": v.int_().min(0).optional(),
|
|
117
|
+
"tags": v.array(v.string()).max_items(5),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
# Raises on failure
|
|
121
|
+
user = User.parse(input_data)
|
|
122
|
+
|
|
123
|
+
# Or get a result object
|
|
124
|
+
result = User.safe_parse(input_data)
|
|
125
|
+
if not result.success:
|
|
126
|
+
print(result.issues)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
</td>
|
|
130
|
+
</tr>
|
|
131
|
+
</table>
|
|
132
|
+
|
|
133
|
+
<details>
|
|
134
|
+
<summary>Go example</summary>
|
|
135
|
+
|
|
136
|
+
```go
|
|
137
|
+
import av "github.com/BetterCorp/AnyVali/sdk/go"
|
|
138
|
+
|
|
139
|
+
User := av.Object(map[string]av.Schema{
|
|
140
|
+
"name": av.String().MinLength(1),
|
|
141
|
+
"email": av.String().Format("email"),
|
|
142
|
+
"age": av.Optional(av.Int().Min(0)),
|
|
143
|
+
"tags": av.Array(av.String()).MaxItems(5),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
result := User.SafeParse(input)
|
|
147
|
+
if !result.Success {
|
|
148
|
+
for _, issue := range result.Issues {
|
|
149
|
+
fmt.Printf("[%s] %s at %v\n", issue.Code, issue.Message, issue.Path)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
</details>
|
|
154
|
+
|
|
155
|
+
## Cross-Language Schema Sharing
|
|
156
|
+
|
|
157
|
+
AnyVali's core feature: export a schema from one language, import it in another.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// TypeScript frontend -- export
|
|
161
|
+
const doc = User.export();
|
|
162
|
+
const json = JSON.stringify(doc);
|
|
163
|
+
// Send to your backend, save to DB, put in a config file...
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# Python backend -- import
|
|
168
|
+
import json, anyvali as v
|
|
169
|
+
|
|
170
|
+
schema = v.import_schema(json.loads(schema_json))
|
|
171
|
+
result = schema.safe_parse(request_body) # Same validation rules!
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Forms
|
|
175
|
+
|
|
176
|
+
The JS SDK also ships a small forms layer for browser-native fields, HTML5 attributes, and AnyVali validation.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { object, string, int } from "@anyvali/js";
|
|
180
|
+
import { initForm } from "@anyvali/js/forms";
|
|
181
|
+
|
|
182
|
+
const Signup = object({
|
|
183
|
+
email: string().format("email"),
|
|
184
|
+
age: int().min(18),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
initForm("#signup", { schema: Signup });
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```html
|
|
191
|
+
<form id="signup">
|
|
192
|
+
<input name="email" type="email" />
|
|
193
|
+
<input name="age" type="number" />
|
|
194
|
+
<button type="submit">Create account</button>
|
|
195
|
+
</form>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
For JSX-style attribute binding:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import { object, string } from "@anyvali/js";
|
|
202
|
+
import { createFormBindings } from "@anyvali/js/forms";
|
|
203
|
+
|
|
204
|
+
const Signup = object({
|
|
205
|
+
email: string().format("email"),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const form = createFormBindings({ schema: Signup });
|
|
209
|
+
|
|
210
|
+
<input {...form.field("email")} />;
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The portable JSON format:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"anyvaliVersion": "1.0",
|
|
218
|
+
"schemaVersion": "1",
|
|
219
|
+
"root": {
|
|
220
|
+
"kind": "object",
|
|
221
|
+
"properties": {
|
|
222
|
+
"name": { "kind": "string", "minLength": 1 },
|
|
223
|
+
"email": { "kind": "string", "format": "email" }
|
|
224
|
+
},
|
|
225
|
+
"required": ["name", "email"],
|
|
226
|
+
"unknownKeys": "reject"
|
|
227
|
+
},
|
|
228
|
+
"definitions": {},
|
|
229
|
+
"extensions": {}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Supported SDKs
|
|
234
|
+
|
|
235
|
+
| Language | Package | Status |
|
|
236
|
+
|----------|---------|--------|
|
|
237
|
+
| JavaScript / TypeScript | [`@anyvali/js`](https://www.npmjs.com/package/@anyvali/js) | v0.0.1 |
|
|
238
|
+
| Python | [`anyvali`](https://pypi.org/project/anyvali/) | v0.0.1 |
|
|
239
|
+
| Go | [`github.com/BetterCorp/AnyVali/sdk/go`](https://pkg.go.dev/github.com/BetterCorp/AnyVali/sdk/go) | v0.0.1 |
|
|
240
|
+
| Java | `com.anyvali:anyvali` | v0.0.1 |
|
|
241
|
+
| C# | [`AnyVali`](https://www.nuget.org/packages/AnyVali) | v0.0.1 |
|
|
242
|
+
| Rust | [`anyvali`](https://crates.io/crates/anyvali) | v0.0.1 |
|
|
243
|
+
| PHP | `anyvali/anyvali` | v0.0.1 |
|
|
244
|
+
| Ruby | [`anyvali`](https://rubygems.org/gems/anyvali) | v0.0.1 |
|
|
245
|
+
| Kotlin | `com.anyvali:anyvali` | v0.0.1 |
|
|
246
|
+
| C++ | `anyvali` (CMake) | v0.0.1 |
|
|
247
|
+
|
|
248
|
+
## CLI & HTTP API
|
|
249
|
+
|
|
250
|
+
Don't need an SDK? Use AnyVali from the command line or as a validation microservice.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Validate from the command line
|
|
254
|
+
anyvali validate schema.json '{"name": "Alice", "email": "alice@test.com"}'
|
|
255
|
+
|
|
256
|
+
# Pipe from stdin
|
|
257
|
+
cat payload.json | anyvali validate schema.json -
|
|
258
|
+
|
|
259
|
+
# Start a validation server
|
|
260
|
+
anyvali serve --port 8080 --schemas ./schemas/
|
|
261
|
+
|
|
262
|
+
# Validate via HTTP
|
|
263
|
+
curl -X POST http://localhost:8080/validate/user \
|
|
264
|
+
-H "Content-Type: application/json" \
|
|
265
|
+
-d '{"name": "Alice", "email": "alice@test.com"}'
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Pre-built binaries for Linux, macOS, and Windows are available on the [releases page](https://github.com/BetterCorp/AnyVali/releases). Docker image: `docker pull anyvali/cli`.
|
|
269
|
+
|
|
270
|
+
See the [CLI Reference](https://docs.anyvali.com/docs/cli) and [HTTP API Reference](https://docs.anyvali.com/docs/api) for full documentation.
|
|
271
|
+
|
|
272
|
+
## Schema Types
|
|
273
|
+
|
|
274
|
+
| Category | Types |
|
|
275
|
+
|----------|-------|
|
|
276
|
+
| **Primitives** | `string`, `bool`, `null` |
|
|
277
|
+
| **Numbers** | `number` (float64), `int` (int64), `float32`, `float64`, `int8`-`int64`, `uint8`-`uint64` |
|
|
278
|
+
| **Special** | `any`, `unknown`, `never` |
|
|
279
|
+
| **Values** | `literal`, `enum` |
|
|
280
|
+
| **Collections** | `array`, `tuple`, `object`, `record` |
|
|
281
|
+
| **Composition** | `union`, `intersection` |
|
|
282
|
+
| **Modifiers** | `optional`, `nullable` |
|
|
283
|
+
|
|
284
|
+
## Documentation
|
|
285
|
+
|
|
286
|
+
| Guide | Description |
|
|
287
|
+
|-------|-------------|
|
|
288
|
+
| [Getting Started](https://docs.anyvali.com) | Installation, API reference, examples |
|
|
289
|
+
| [Numeric Semantics](https://docs.anyvali.com/docs/numeric-semantics) | Why `number` = float64 and `int` = int64 |
|
|
290
|
+
| [Portability Guide](https://docs.anyvali.com/docs/portability-guide) | Design schemas that work across all languages |
|
|
291
|
+
| [SDK Authors Guide](https://docs.anyvali.com/docs/sdk-authors-guide) | Implement a new AnyVali SDK |
|
|
292
|
+
| [Canonical Spec](https://docs.anyvali.com/spec/spec) | The normative specification |
|
|
293
|
+
| [JSON Format](https://docs.anyvali.com/spec/json-format) | Interchange format details |
|
|
294
|
+
| [CLI Reference](https://docs.anyvali.com/docs/cli) | Command-line validation tool |
|
|
295
|
+
| [HTTP API](https://docs.anyvali.com/docs/api) | Validation microservice / sidecar |
|
|
296
|
+
| [Development](https://docs.anyvali.com/docs/development) | Building, testing, contributing |
|
|
297
|
+
|
|
298
|
+
## Repository Layout
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
.
|
|
302
|
+
├── docs/ Documentation guides
|
|
303
|
+
├── spec/ Canonical spec, JSON format, conformance corpus
|
|
304
|
+
├── sdk/
|
|
305
|
+
│ ├── js/ JavaScript / TypeScript SDK
|
|
306
|
+
│ ├── python/ Python SDK
|
|
307
|
+
│ ├── go/ Go SDK
|
|
308
|
+
│ ├── java/ Java SDK
|
|
309
|
+
│ ├── csharp/ C# SDK
|
|
310
|
+
│ ├── rust/ Rust SDK
|
|
311
|
+
│ ├── php/ PHP SDK
|
|
312
|
+
│ ├── ruby/ Ruby SDK
|
|
313
|
+
│ ├── kotlin/ Kotlin SDK
|
|
314
|
+
│ └── cpp/ C++ SDK
|
|
315
|
+
├── cli/ CLI binary and HTTP API server (Go)
|
|
316
|
+
├── runner.sh Build/test/CI runner
|
|
317
|
+
└── site/ anyvali.com source
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Contributing
|
|
321
|
+
|
|
322
|
+
Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
./runner.sh help # See all commands
|
|
326
|
+
./runner.sh test js # Test a specific SDK
|
|
327
|
+
./runner.sh ci # Run the full CI pipeline locally
|
|
328
|
+
pwsh -File tools/release/build_release.ps1 # Build release artifacts with Docker
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## License
|
|
332
|
+
|
|
333
|
+
AnyVali is licensed under the [MIT License](LICENSE).
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
<p align="center">
|
|
338
|
+
<a href="https://anyvali.com">anyvali.com</a>
|
|
339
|
+
</p>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnyVali
|
|
4
|
+
class AnyValiDocument
|
|
5
|
+
ANYVALI_VERSION = "1.0"
|
|
6
|
+
SCHEMA_VERSION = "1"
|
|
7
|
+
|
|
8
|
+
attr_reader :anyvali_version, :schema_version, :root, :definitions, :extensions
|
|
9
|
+
|
|
10
|
+
def initialize(root:, definitions: {}, extensions: {}, anyvali_version: ANYVALI_VERSION, schema_version: SCHEMA_VERSION)
|
|
11
|
+
@anyvali_version = anyvali_version
|
|
12
|
+
@schema_version = schema_version
|
|
13
|
+
@root = root
|
|
14
|
+
@definitions = definitions
|
|
15
|
+
@extensions = extensions
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
{
|
|
20
|
+
"anyvaliVersion" => @anyvali_version,
|
|
21
|
+
"schemaVersion" => @schema_version,
|
|
22
|
+
"root" => node_to_h(@root),
|
|
23
|
+
"definitions" => @definitions.transform_values { |v| node_to_h(v) },
|
|
24
|
+
"extensions" => @extensions
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_json(*_args)
|
|
29
|
+
require "json"
|
|
30
|
+
JSON.generate(to_h)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def node_to_h(node)
|
|
36
|
+
case node
|
|
37
|
+
when Hash then node
|
|
38
|
+
when Schema then node.to_node
|
|
39
|
+
else node
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "ipaddr"
|
|
5
|
+
require "date"
|
|
6
|
+
|
|
7
|
+
module AnyVali
|
|
8
|
+
module Format
|
|
9
|
+
module Validators
|
|
10
|
+
EMAIL_REGEX = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
|
|
11
|
+
UUID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
12
|
+
# Strict IPv4: no leading zeros, octets 0-255
|
|
13
|
+
IPV4_REGEX = /\A(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\z/
|
|
14
|
+
DATETIME_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})\z/
|
|
15
|
+
DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def validate(value, format, path, issues)
|
|
20
|
+
valid = case format
|
|
21
|
+
when "email" then valid_email?(value)
|
|
22
|
+
when "url" then valid_url?(value)
|
|
23
|
+
when "uuid" then valid_uuid?(value)
|
|
24
|
+
when "ipv4" then valid_ipv4?(value)
|
|
25
|
+
when "ipv6" then valid_ipv6?(value)
|
|
26
|
+
when "date" then valid_date?(value)
|
|
27
|
+
when "date-time" then valid_datetime?(value)
|
|
28
|
+
else false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
unless valid
|
|
32
|
+
issues << ValidationIssue.new(
|
|
33
|
+
code: IssueCodes::INVALID_STRING,
|
|
34
|
+
path: path,
|
|
35
|
+
expected: format,
|
|
36
|
+
received: value
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def valid_email?(value)
|
|
42
|
+
EMAIL_REGEX.match?(value)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def valid_url?(value)
|
|
46
|
+
uri = URI.parse(value)
|
|
47
|
+
%w[http https].include?(uri.scheme) && !uri.host.nil?
|
|
48
|
+
rescue URI::InvalidURIError
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def valid_uuid?(value)
|
|
53
|
+
UUID_REGEX.match?(value)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def valid_ipv4?(value)
|
|
57
|
+
# Must match strict pattern (no leading zeros)
|
|
58
|
+
return false unless IPV4_REGEX.match?(value)
|
|
59
|
+
# Check for leading zeros explicitly
|
|
60
|
+
parts = value.split(".")
|
|
61
|
+
parts.length == 4 && parts.all? { |p| p == "0" || !p.start_with?("0") }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def valid_ipv6?(value)
|
|
65
|
+
addr = IPAddr.new(value)
|
|
66
|
+
addr.ipv6?
|
|
67
|
+
rescue IPAddr::InvalidAddressError, IPAddr::AddressFamilyError
|
|
68
|
+
false
|
|
69
|
+
rescue ArgumentError
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def valid_date?(value)
|
|
74
|
+
return false unless DATE_REGEX.match?(value)
|
|
75
|
+
Date.parse(value)
|
|
76
|
+
true
|
|
77
|
+
rescue Date::Error, ArgumentError
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def valid_datetime?(value)
|
|
82
|
+
return false unless DATETIME_REGEX.match?(value)
|
|
83
|
+
# Validate the date part
|
|
84
|
+
date_part = value[0..9]
|
|
85
|
+
valid_date?(date_part)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module AnyVali
|
|
6
|
+
module Interchange
|
|
7
|
+
module Exporter
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def export(schema, mode: :portable, definitions: {})
|
|
11
|
+
if mode == :portable && !schema.portable?
|
|
12
|
+
raise ValidationError, [
|
|
13
|
+
ValidationIssue.new(
|
|
14
|
+
code: IssueCodes::CUSTOM_VALIDATION_NOT_PORTABLE,
|
|
15
|
+
expected: "portable schema",
|
|
16
|
+
received: "schema with custom validators"
|
|
17
|
+
)
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
doc = {
|
|
22
|
+
"anyvaliVersion" => AnyValiDocument::ANYVALI_VERSION,
|
|
23
|
+
"schemaVersion" => AnyValiDocument::SCHEMA_VERSION,
|
|
24
|
+
"root" => schema_to_node(schema),
|
|
25
|
+
"definitions" => definitions.transform_values { |v| schema_to_node(v) },
|
|
26
|
+
"extensions" => {}
|
|
27
|
+
}
|
|
28
|
+
doc
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_json(schema, mode: :portable, definitions: {})
|
|
32
|
+
JSON.generate(export(schema, mode: mode, definitions: definitions))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def schema_to_node(schema)
|
|
36
|
+
schema.to_node
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|