README.md

# ExJsonschema

🚀 **High-performance JSON Schema validation for Elixir using Rust**

ExJsonschema is a fast, safe, and spec-compliant JSON Schema validator for Elixir, powered by the battle-tested Rust [`jsonschema`](https://crates.io/crates/jsonschema) crate. It provides an idiomatic Elixir API with detailed error reporting and supports multiple JSON Schema draft versions.

## ✨ Features

- **🔥 High Performance**: Rust-powered validation with zero-copy JSON processing
- **🛡️ Memory Safe**: No risk of crashing the BEAM VM - all Rust panics are caught
- **📋 Spec Compliant**: Supports JSON Schema draft-07, draft 2019-09, and draft 2020-12
- **🔍 Detailed Errors**: Rich error messages with path information and validation context
- **📦 Zero Dependencies**: Precompiled NIFs mean no Rust toolchain required for end users
- **🎯 Idiomatic Elixir**: Clean, functional API that feels natural in Elixir

## 📋 Installation

Add `ex_jsonschema` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ex_jsonschema, "~> 0.1.0"}
  ]
end
```

That's it! No Rust toolchain required - precompiled binaries are included.

## 🚀 Quick Start

```elixir
# Compile a schema once
schema = ~s({
  "type": "object",
  "properties": {
    "name": {"type": "string", "minLength": 1},
    "age": {"type": "number", "minimum": 0}
  },
  "required": ["name"]
})

{:ok, compiled} = ExJsonschema.compile(schema)

# Validate JSON - fast validation
valid_json = ~s({"name": "Alice", "age": 30})
:ok = ExJsonschema.validate(compiled, valid_json)

# Invalid JSON with detailed errors
invalid_json = ~s({"age": -5})
{:error, errors} = ExJsonschema.validate(compiled, invalid_json)

Enum.each(errors, fn error ->
  IO.puts("❌ #{error.message} at #{error.instance_path}")
end)
# Output:
# ❌ "name" is a required property at 
# ❌ -5 is less than the minimum of 0 at /age
```

## 📖 API Reference

### Schema Compilation

```elixir
# Compile a schema
{:ok, compiled} = ExJsonschema.compile(schema_json)

# Compile with error handling
case ExJsonschema.compile(schema_json) do
  {:ok, compiled} -> 
    # Use compiled schema
  {:error, %ExJsonschema.CompilationError{} = error} ->
    IO.puts("Schema error: #{error}")
end

# Compile and raise on error
compiled = ExJsonschema.compile!(schema_json)
```

### Validation

```elixir
# Full validation with detailed errors
case ExJsonschema.validate(compiled, json) do
  :ok -> 
    IO.puts("Valid!")
  {:error, errors} ->
    Enum.each(errors, &IO.puts("Error: #{&1}"))
end

# Quick validity check (faster)
if ExJsonschema.valid?(compiled, json) do
  IO.puts("Valid!")
end

# One-shot validation (compile + validate)
ExJsonschema.validate_once(schema_json, instance_json)

# Validation with exceptions
ExJsonschema.validate!(compiled, json)
```

## 🔍 Enhanced Error Handling

ExJsonschema provides detailed error information to help you debug validation issues:

### Schema Compilation Errors

```elixir
# JSON parsing error
invalid_json = ~s({"type": "string)  # Missing quote
{:error, error} = ExJsonschema.compile(invalid_json)

error.type     # :json_parse_error
error.message  # "Invalid JSON: EOF while parsing a string at line 1 column 16"
error.details  # "Failed to parse JSON at line 1, column 16"

# Schema validation error  
invalid_schema = ~s({"type": "invalid_type"})
{:error, error} = ExJsonschema.compile(invalid_schema)

error.type     # :compilation_error  
error.message  # "Schema compilation failed"
error.details  # "\"invalid_type\" is not valid under any of the schemas listed in the 'anyOf' keyword"
```

### Validation Errors

```elixir
schema = ~s({
  "type": "object",
  "properties": {
    "user": {
      "type": "object", 
      "properties": {
        "age": {"type": "number", "minimum": 0}
      }
    }
  }
})

{:ok, compiled} = ExJsonschema.compile(schema)
{:error, [error]} = ExJsonschema.validate(compiled, ~s({"user": {"age": -5}}))

error.instance_path  # "/user/age"
error.schema_path    # "/properties/user/properties/age/minimum"  
error.message        # "-5 is less than the minimum of 0"
```

## 🏗️ Building from Source

For development or if you need to build from source:

```bash
# Clone the repository
git clone https://github.com/your-username/ex_jsonschema.git
cd ex_jsonschema

# Install dependencies
mix deps.get

# Force build from source (requires Rust toolchain)
EX_JSONSCHEMA_BUILD=1 mix compile

# Run tests
EX_JSONSCHEMA_BUILD=1 mix test
```

## 🎯 Performance

ExJsonschema is designed for high-performance applications:

- **Compiled schemas**: Compile once, validate many times
- **Zero-copy**: Direct validation of JSON strings without intermediate parsing
- **Rust performance**: Orders of magnitude faster than pure Elixir implementations
- **Memory efficient**: Minimal memory allocation during validation

## 🤝 JSON Schema Support

ExJsonschema supports multiple JSON Schema draft versions:

- ✅ **Draft 7** (2019)
- ✅ **Draft 2019-09** 
- ✅ **Draft 2020-12** (latest)

All core keywords and validation rules are supported, including:
- Type validation (`string`, `number`, `object`, `array`, etc.)
- Constraints (`minimum`, `maximum`, `minLength`, `maxLength`, etc.)
- Object validation (`properties`, `required`, `additionalProperties`, etc.)
- Array validation (`items`, `minItems`, `maxItems`, etc.)
- String formats (`email`, `uri`, `date-time`, etc.)
- Conditional schemas (`if`/`then`/`else`, `allOf`, `anyOf`, `oneOf`)

## 📚 Examples

### User Registration Validation

```elixir
user_schema = ~s({
  "type": "object",
  "properties": {
    "email": {"type": "string", "format": "email"},
    "username": {"type": "string", "minLength": 3, "maxLength": 20},
    "age": {"type": "integer", "minimum": 13, "maximum": 120},
    "preferences": {
      "type": "object",
      "properties": {
        "newsletter": {"type": "boolean"},
        "theme": {"type": "string", "enum": ["light", "dark"]}
      }
    }
  },
  "required": ["email", "username", "age"]
})

{:ok, compiled} = ExJsonschema.compile(user_schema)

# Valid user
user_data = ~s({
  "email": "alice@example.com",
  "username": "alice_cooper", 
  "age": 25,
  "preferences": {
    "newsletter": true,
    "theme": "dark"
  }
})

:ok = ExJsonschema.validate(compiled, user_data)
```

### API Response Validation

```elixir
api_response_schema = ~s({
  "type": "object",
  "properties": {
    "status": {"type": "string", "enum": ["success", "error"]},
    "data": {"type": "object"},
    "message": {"type": "string"},
    "timestamp": {"type": "string", "format": "date-time"}
  },
  "required": ["status", "timestamp"],
  "if": {"properties": {"status": {"const": "error"}}},
  "then": {"required": ["message"]},
  "else": {"required": ["data"]}
})
```

## 🔧 Development

### Requirements

- Elixir 1.12+
- Erlang/OTP 22+
- Rust 1.70+ (only for building from source)

### Running Tests

```bash
mix test                    # Uses precompiled NIF
EX_JSONSCHEMA_BUILD=1 mix test  # Builds from source
```

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## 🙏 Acknowledgments

- Built on the excellent [jsonschema](https://crates.io/crates/jsonschema) Rust crate
- Powered by [Rustler](https://github.com/rusterlium/rustler) for safe Rust-Elixir interop
- Inspired by the JSON Schema specification and community