diff --git a/docs/docs.json b/docs/docs.json index c124427..1007b9e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -59,14 +59,6 @@ "json-schema/code-generation/from-enums", "json-schema/code-generation/from-json" ] - }, - { - "group": "Validation", - "pages": [ - "json-schema/validation/data-validation", - "json-schema/validation/error-handling", - "json-schema/validation/version-features" - ] } ] } diff --git a/docs/json-schema/advanced/conditional-validation.mdx b/docs/json-schema/advanced/conditional-validation.mdx index 004cd17..79bfbff 100644 --- a/docs/json-schema/advanced/conditional-validation.mdx +++ b/docs/json-schema/advanced/conditional-validation.mdx @@ -628,22 +628,3 @@ $complexSchema = Schema::object('complex_validation') Different validation rules for different API versions or client types. - -## Next Steps - - - - Learn to create reusable schema components for complex conditional logic - - - Explore property-dependent validation in modern JSON Schema versions - - diff --git a/docs/json-schema/advanced/definitions-refs.mdx b/docs/json-schema/advanced/definitions-refs.mdx index 38ca420..50c9e49 100644 --- a/docs/json-schema/advanced/definitions-refs.mdx +++ b/docs/json-schema/advanced/definitions-refs.mdx @@ -539,22 +539,3 @@ $schema->addDefinition( Reuse field validation patterns across different forms and input structures. - -## Next Steps - - - - Learn how to use definitions with conditional validation logic - - - Generate schemas with definitions from PHP classes - - diff --git a/docs/json-schema/advanced/dependent-schemas.mdx b/docs/json-schema/advanced/dependent-schemas.mdx index 43abe3f..70ae0c4 100644 --- a/docs/json-schema/advanced/dependent-schemas.mdx +++ b/docs/json-schema/advanced/dependent-schemas.mdx @@ -563,22 +563,3 @@ $schema->dependentSchemas([ Profile settings where enabled features require additional information. - -## Next Steps - - - - Learn about regex-based property validation patterns - - - Master if/then/else and other conditional validation techniques - - diff --git a/docs/json-schema/advanced/pattern-properties.mdx b/docs/json-schema/advanced/pattern-properties.mdx index 5c71f0c..25a8a86 100644 --- a/docs/json-schema/advanced/pattern-properties.mdx +++ b/docs/json-schema/advanced/pattern-properties.mdx @@ -489,22 +489,3 @@ $schema->patternProperties([ Allow users to define custom fields that follow specific naming rules. - -## Next Steps - - - - Learn more about object validation and additional properties - - - Generate schemas with pattern properties from PHP classes - - diff --git a/docs/json-schema/advanced/string-formats.mdx b/docs/json-schema/advanced/string-formats.mdx index e6b4ae4..3a23a71 100644 --- a/docs/json-schema/advanced/string-formats.mdx +++ b/docs/json-schema/advanced/string-formats.mdx @@ -211,22 +211,3 @@ $userProfileSchema->isValid($validProfile); // true Validate formatted data during import processes with appropriate type checking. - -## Next Steps - - - - Learn more about string validation and pattern matching - - - Master data validation techniques and error handling - - diff --git a/docs/json-schema/advanced/unevaluated-properties.mdx b/docs/json-schema/advanced/unevaluated-properties.mdx index cff31ad..6c5720f 100644 --- a/docs/json-schema/advanced/unevaluated-properties.mdx +++ b/docs/json-schema/advanced/unevaluated-properties.mdx @@ -423,22 +423,3 @@ $schema = Schema::object('strict_user', SchemaVersion::Draft_2019_09) Validate data during migration with strict control over allowed properties and transformations. - -## Next Steps - - - - Learn about property-dependent validation in modern JSON Schema - - - Understand JSON Schema version compatibility and feature support - - diff --git a/docs/json-schema/code-generation/from-classes.mdx b/docs/json-schema/code-generation/from-classes.mdx index dd92a73..9110b86 100644 --- a/docs/json-schema/code-generation/from-classes.mdx +++ b/docs/json-schema/code-generation/from-classes.mdx @@ -512,22 +512,3 @@ Validation rules like minLength, pattern, format are not extracted from docblock Generate schemas for application configuration classes. - -## Next Steps - - - - Learn to generate schemas from function signatures - - - Generate validation schemas from PHP enums - - diff --git a/docs/json-schema/code-generation/from-closures.mdx b/docs/json-schema/code-generation/from-closures.mdx index 3614e05..f2f3803 100644 --- a/docs/json-schema/code-generation/from-closures.mdx +++ b/docs/json-schema/code-generation/from-closures.mdx @@ -473,22 +473,3 @@ function handleUserStuff($data, $action, $options = []) {} Generate validation for configuration and setup functions. - -## Next Steps - - - - Generate validation schemas from PHP enums - - - Import and work with existing JSON Schema definitions - - diff --git a/docs/json-schema/code-generation/from-enums.mdx b/docs/json-schema/code-generation/from-enums.mdx index 2c22a8a..b847f2f 100644 --- a/docs/json-schema/code-generation/from-enums.mdx +++ b/docs/json-schema/code-generation/from-enums.mdx @@ -635,22 +635,3 @@ try { Generate client-side validation and form options from backend enums. - -## Next Steps - - - - Import and work with existing JSON Schema definitions - - - Learn advanced validation techniques for enum values - - diff --git a/docs/json-schema/code-generation/from-json.mdx b/docs/json-schema/code-generation/from-json.mdx index 977e3f0..51996ad 100644 --- a/docs/json-schema/code-generation/from-json.mdx +++ b/docs/json-schema/code-generation/from-json.mdx @@ -237,123 +237,6 @@ $singleUserResponse = [ $apiSchema->isValid($singleUserResponse); // true ``` -## Loading from Files - -Load schemas from external files: - -```php -/** - * Load schema from file - */ -function loadSchemaFromFile(string $filepath): Schema -{ - if (!file_exists($filepath)) { - throw new InvalidArgumentException("Schema file not found: {$filepath}"); - } - - $content = file_get_contents($filepath); - $schemaData = json_decode($content, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new InvalidArgumentException("Invalid JSON in schema file: " . json_last_error_msg()); - } - - return Schema::fromJson($schemaData); -} - -// Usage -$userSchema = loadSchemaFromFile('schemas/user.json'); -$productSchema = loadSchemaFromFile('schemas/product.json'); -$orderSchema = loadSchemaFromFile('schemas/order.json'); -``` - -## Best Practices - -### 1. Validate JSON Before Import - -```php -function safeJsonImport(string $jsonString): Schema -{ - $data = json_decode($jsonString, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new InvalidArgumentException( - "Invalid JSON: " . json_last_error_msg() - ); - } - - // Basic schema validation - if (!isset($data['type'])) { - throw new InvalidArgumentException("Schema must have a 'type' property"); - } - - return Schema::fromJson($data); -} -``` - -### 2. Handle Schema Dependencies - -```php -function resolveSchemaReferences(array $schema, array $definitions = []): array -{ - // Recursively resolve $ref properties - foreach ($schema as $key => $value) { - if ($key === '$ref' && is_string($value)) { - // Resolve reference to actual schema - $refPath = str_replace('#/definitions/', '', $value); - if (isset($definitions[$refPath])) { - return $definitions[$refPath]; - } - } elseif (is_array($value)) { - $schema[$key] = resolveSchemaReferences($value, $definitions); - } - } - - return $schema; -} -``` - -### 3. Version Compatibility Checking - -```php -function checkVersionCompatibility(array $schema): void -{ - $schemaVersion = $schema['$schema'] ?? ''; - - $supportedVersions = [ - 'http://json-schema.org/draft-07/schema#', - 'https://json-schema.org/draft/2019-09/schema', - 'https://json-schema.org/draft/2020-12/schema' - ]; - - if ($schemaVersion && !in_array($schemaVersion, $supportedVersions)) { - throw new InvalidArgumentException( - "Unsupported schema version: {$schemaVersion}" - ); - } -} -``` - -### 4. Schema Caching - -```php -class CachedSchemaLoader -{ - private array $cache = []; - - public function load(string $filepath): Schema - { - $cacheKey = md5($filepath . filemtime($filepath)); - - if (!isset($this->cache[$cacheKey])) { - $this->cache[$cacheKey] = loadSchemaFromFile($filepath); - } - - return $this->cache[$cacheKey]; - } -} -``` - ## Common Use Cases @@ -370,22 +253,3 @@ class CachedSchemaLoader Work with external JSON schemas from APIs, services, or documentation. - -## Next Steps - - - - Learn to validate data with imported schemas - - - Explore the complete Schema Factory API reference - - diff --git a/docs/json-schema/installation.mdx b/docs/json-schema/installation.mdx index 4efef6d..e6aeb3b 100644 --- a/docs/json-schema/installation.mdx +++ b/docs/json-schema/installation.mdx @@ -13,15 +13,9 @@ icon: 'terminal' - Install the package using Composer with one of these commands: - ```bash composer require cortexphp/json-schema ``` - - - Verify the package was installed by running: `composer show cortexphp/json-schema` - @@ -72,11 +66,3 @@ If you're contributing to the package or want to run tests: ## Next Steps Now that you have the package installed, you're ready to create your first JSON Schema: - - - Learn how to create and validate schemas with practical examples - diff --git a/docs/json-schema/introduction.mdx b/docs/json-schema/introduction.mdx index 168f515..fedcd91 100644 --- a/docs/json-schema/introduction.mdx +++ b/docs/json-schema/introduction.mdx @@ -30,7 +30,6 @@ icon: 'book-open' Validate data against schemas with detailed error messages and boolean validation methods. @@ -70,7 +69,6 @@ icon: 'book-open' Automatic validation of version-specific features with helpful error messages. @@ -180,22 +178,3 @@ The package defaults to Draft 2020-12 for the latest features. Use Draft-07 for | `unevaluatedProperties` | ❌ | ✅ | ✅ | | `dependentSchemas` | ❌ | ✅ | ✅ | | `prefixItems` | ❌ | ❌ | ✅ | - -## Next Steps - - - - Install the package and create your first schema - - - Learn the basics with practical examples - - diff --git a/docs/json-schema/mcp-server.mdx b/docs/json-schema/mcp-server.mdx index 8e282ec..5199956 100644 --- a/docs/json-schema/mcp-server.mdx +++ b/docs/json-schema/mcp-server.mdx @@ -277,22 +277,3 @@ The MCP server provides access to comprehensive documentation, code examples, an -## Additional Resources - - - - Browse the complete CortexPHP documentation - - - Learn more about the Model Context Protocol - - - diff --git a/docs/json-schema/quickstart.mdx b/docs/json-schema/quickstart.mdx index c6517b6..4ed42b3 100644 --- a/docs/json-schema/quickstart.mdx +++ b/docs/json-schema/quickstart.mdx @@ -172,9 +172,9 @@ Use `toArray()` when you need to programmatically work with the schema structure $usernameSchema->isValid('john-doe'); // false (contains hyphen) ``` - + String schemas support patterns, length constraints, and format validation. - + @@ -193,9 +193,9 @@ Use `toArray()` when you need to programmatically work with the schema structure $tagsSchema->isValid([]); // false (too few items) ``` - + Array schemas can validate item types, count limits, and uniqueness. - + @@ -215,9 +215,9 @@ Use `toArray()` when you need to programmatically work with the schema structure ); ``` - + Object schemas support unlimited nesting and property requirements. - + @@ -227,7 +227,7 @@ You can also use schema classes directly instead of the factory: -```php Factory Method (Recommended) +```php Factory Method use Cortex\JsonSchema\Schema; $schema = Schema::string('name') @@ -354,15 +354,4 @@ Schema::setDefaultVersion(SchemaVersion::Draft_07); // For maximum compatibility > Generate schemas automatically from PHP classes and functions - - Master data validation and error handling techniques - - - -The Schema class provides the most convenient way to create schemas. All examples in this documentation use the factory methods unless otherwise specified. - diff --git a/docs/json-schema/schema-types/array.mdx b/docs/json-schema/schema-types/array.mdx index 5f43e62..604478f 100644 --- a/docs/json-schema/schema-types/array.mdx +++ b/docs/json-schema/schema-types/array.mdx @@ -535,22 +535,3 @@ try { Validate complex nested structures like menu trees or organizational hierarchies. - -## Next Steps - - - - Learn about validating complex object structures - - - Explore advanced array validation features in modern JSON Schema versions - - diff --git a/docs/json-schema/schema-types/boolean.mdx b/docs/json-schema/schema-types/boolean.mdx index c230d70..e17b6d8 100644 --- a/docs/json-schema/schema-types/boolean.mdx +++ b/docs/json-schema/schema-types/boolean.mdx @@ -400,22 +400,3 @@ $booleanSchema->isValid(false); // true Track record states, completion status, and operational flags. - -## Next Steps - - - - Learn about null value validation - - - Use booleans in conditional schema logic - - diff --git a/docs/json-schema/schema-types/integer.mdx b/docs/json-schema/schema-types/integer.mdx index 930d658..9e2cb16 100644 --- a/docs/json-schema/schema-types/integer.mdx +++ b/docs/json-schema/schema-types/integer.mdx @@ -366,22 +366,3 @@ try { Validate integer configuration parameters like ports, timeouts, and limits. - -## Next Steps - - - - Learn about number validation for decimal values - - - Validate boolean true/false values - - diff --git a/docs/json-schema/schema-types/null.mdx b/docs/json-schema/schema-types/null.mdx index 93d050d..a8f83bb 100644 --- a/docs/json-schema/schema-types/null.mdx +++ b/docs/json-schema/schema-types/null.mdx @@ -348,22 +348,3 @@ $processedDataSchema = Schema::object('processed_data') Handle API fields that may not be present or applicable in all responses. - -## Next Steps - - - - Learn about combining multiple types including null - - - Use null in conditional schema logic - - diff --git a/docs/json-schema/schema-types/number.mdx b/docs/json-schema/schema-types/number.mdx index 2a7d8a7..da7a345 100644 --- a/docs/json-schema/schema-types/number.mdx +++ b/docs/json-schema/schema-types/number.mdx @@ -295,7 +295,7 @@ try { ## Number vs Integer -Numbers can be integers or floating-point values. If you specifically need integer validation, use [Integer Schema](/schema-types/integer) instead. +Numbers can be integers or floating-point values. If you specifically need integer validation, use [Integer Schema](/json-schema/schema-types/integer) instead. ```php @@ -328,22 +328,3 @@ $integerSchema->isValid(42.5); // false (not whole number) Validate numeric configuration parameters with appropriate ranges and precision. - -## Next Steps - - - - Learn about integer-specific validation and constraints - - - Validate arrays of numbers and other complex data structures - - diff --git a/docs/json-schema/schema-types/object.mdx b/docs/json-schema/schema-types/object.mdx index fe9473b..2f67720 100644 --- a/docs/json-schema/schema-types/object.mdx +++ b/docs/json-schema/schema-types/object.mdx @@ -519,6 +519,12 @@ $schema->isValid([ ## Common Use Cases + + Define schemas for LLM responses to ensure consistent, parseable structured data from AI models. + + + Specify function parameters for AI agents and assistants to enable reliable tool invocation. + Validate JSON API payloads with structured data and specific field requirements. @@ -532,22 +538,3 @@ $schema->isValid([ Validate data models before persistence with property and constraint validation. - -## Next Steps - - - - Learn about array validation and nested data structures - - - Explore pattern properties and advanced object validation - - diff --git a/docs/json-schema/schema-types/string.mdx b/docs/json-schema/schema-types/string.mdx index 60cb4c9..2b1a8aa 100644 --- a/docs/json-schema/schema-types/string.mdx +++ b/docs/json-schema/schema-types/string.mdx @@ -64,10 +64,6 @@ Set minimum and maximum character limits for your strings: ``` - -Length constraints help ensure data fits database fields and meets business requirements. - - ## Pattern Matching Use regular expressions to validate string patterns: @@ -296,22 +292,3 @@ $uuidSchema = Schema::string('id', SchemaVersion::Draft_2019_09) Validate imported text data against expected patterns and formats before processing. - -## Next Steps - - - - Learn about numeric validation with ranges and constraints - - - Comprehensive guide to all available string formats - - diff --git a/docs/json-schema/schema-types/union.mdx b/docs/json-schema/schema-types/union.mdx index a7dfd2a..368da76 100644 --- a/docs/json-schema/schema-types/union.mdx +++ b/docs/json-schema/schema-types/union.mdx @@ -413,22 +413,3 @@ $shapeSchema = Schema::union([SchemaType::Object], 'shape') Model data structures with multiple possible shapes or types. - -## Next Steps - - - - Learn advanced conditional logic with anyOf, oneOf, and allOf - - - Master validation techniques for complex union types - - diff --git a/docs/json-schema/validation/data-validation.mdx b/docs/json-schema/validation/data-validation.mdx deleted file mode 100644 index 389e139..0000000 --- a/docs/json-schema/validation/data-validation.mdx +++ /dev/null @@ -1,241 +0,0 @@ ---- -title: Data Validation -description: 'Master data validation techniques with validate() and isValid() methods' -icon: 'check' ---- - -Data validation is the core functionality of JSON Schema. Learn how to validate data against your schemas, handle validation results, and work with validation errors effectively. - -## Basic Validation Methods - -Every schema provides two primary validation methods: - -```php -use Cortex\JsonSchema\Schema; -use Cortex\JsonSchema\Exceptions\SchemaException; - -$schema = Schema::string('name') - ->minLength(2) - ->maxLength(50) - ->required(); - -// Method 1: isValid() - returns boolean -$isValid = $schema->isValid('John Doe'); // true -$isValid = $schema->isValid('J'); // false - -// Method 2: validate() - throws exception on failure -try { - $schema->validate('John Doe'); - echo "Valid!"; -} catch (SchemaException $e) { - echo "Invalid: " . $e->getMessage(); -} -``` - -## Using isValid() for Boolean Checks - -The `isValid()` method is perfect for simple true/false validation: - -```php -$emailSchema = Schema::string('email') - ->format(SchemaFormat::Email) - ->required(); - -$emails = [ - 'valid@example.com', - 'invalid-email', - 'another@test.org' -]; - -$validEmails = []; -foreach ($emails as $email) { - if ($emailSchema->isValid($email)) { - $validEmails[] = $email; - } -} - -// $validEmails contains: ['valid@example.com', 'another@test.org'] -``` - -### Conditional Logic with isValid() - -```php -$userSchema = Schema::object('user') - ->properties( - Schema::string('name')->required(), - Schema::integer('age')->minimum(18)->required(), - Schema::string('email')->format(SchemaFormat::Email)->required() - ); - -$userData = [ - 'name' => 'John Doe', - 'age' => 25, - 'email' => 'john@example.com' -]; - -if ($userSchema->isValid($userData)) { - // Process valid user data - echo "Processing user: " . $userData['name']; -} else { - // Handle invalid data - echo "User data is invalid"; -} -``` - -## Using validate() for Detailed Error Information - -The `validate()` method throws descriptive exceptions when validation fails: - -```php -$productSchema = Schema::object('product') - ->properties( - Schema::string('name') - ->minLength(1) - ->maxLength(100) - ->required(), - Schema::number('price') - ->minimum(0) - ->multipleOf(0.01) - ->required(), - Schema::array('tags') - ->items(Schema::string()) - ->maxItems(10) - ) - ->additionalProperties(false); - -$productData = [ - 'name' => '', // Too short - 'price' => -10, // Below minimum - 'tags' => ['electronics', 'mobile', /* 9 more tags */], // Too many items - 'invalid_field' => 'not allowed' // Additional property -]; - -try { - $productSchema->validate($productData); -} catch (SchemaException $e) { - echo "Validation error: " . $e->getMessage(); - // "String must have minimum length of 1" - - // Access detailed error information - $errors = $e->getErrors(); - print_r($errors); -} -``` - -## Validating Complex Data Structures - -### Object Validation - -```php -$orderSchema = Schema::object('order') - ->properties( - Schema::string('id')->required(), - Schema::object('customer') - ->properties( - Schema::string('name')->required(), - Schema::string('email')->format(SchemaFormat::Email)->required() - ) - ->required(), - Schema::array('items') - ->items( - Schema::object() - ->properties( - Schema::string('sku')->required(), - Schema::integer('quantity')->minimum(1)->required(), - Schema::number('price')->minimum(0)->required() - ) - ) - ->minItems(1) - ->required() - ); - -$orderData = [ - 'id' => 'ORD-123', - 'customer' => [ - 'name' => 'John Doe', - 'email' => 'john@example.com' - ], - 'items' => [ - [ - 'sku' => 'PROD-001', - 'quantity' => 2, - 'price' => 29.99 - ], - [ - 'sku' => 'PROD-002', - 'quantity' => 1, - 'price' => 15.50 - ] - ] -]; - -if ($orderSchema->isValid($orderData)) { - echo "Order is valid"; -} else { - try { - $orderSchema->validate($orderData); - } catch (SchemaException $e) { - echo "Order validation failed: " . $e->getMessage(); - } -} -``` - -### Array Validation - -```php -$scoresSchema = Schema::array('test_scores') - ->items( - Schema::object() - ->properties( - Schema::string('student_id')->required(), - Schema::integer('score') - ->minimum(0) - ->maximum(100) - ->required(), - Schema::string('subject') - ->enum(['math', 'science', 'english', 'history']) - ->required() - ) - ) - ->minItems(1) - ->uniqueItems(true); - -$scoresData = [ - [ - 'student_id' => 'STU-001', - 'score' => 95, - 'subject' => 'math' - ], - [ - 'student_id' => 'STU-002', - 'score' => 87, - 'subject' => 'science' - ] -]; - -try { - $scoresSchema->validate($scoresData); - echo "All scores are valid"; -} catch (SchemaException $e) { - echo "Score validation error: " . $e->getMessage(); -} -``` - -## Next Steps - - - - Learn advanced error handling and custom exception management - - - Explore the complete Schema Factory API reference - - diff --git a/docs/json-schema/validation/error-handling.mdx b/docs/json-schema/validation/error-handling.mdx deleted file mode 100644 index de37f01..0000000 --- a/docs/json-schema/validation/error-handling.mdx +++ /dev/null @@ -1,739 +0,0 @@ ---- -title: Error Handling -description: 'Advanced error handling and exception management for schema validation' -icon: 'x' ---- - -Master error handling in JSON Schema validation with detailed exception information, custom error messages, and graceful error recovery patterns. - -## SchemaException Overview - -All validation errors throw `SchemaException` with detailed information: - -```php -use Cortex\JsonSchema\Schema; -use Cortex\JsonSchema\Exceptions\SchemaException; - -$schema = Schema::string('email') - ->format(SchemaFormat::Email) - ->minLength(5) - ->maxLength(100) - ->required(); - -try { - $schema->validate('invalid-email-format'); -} catch (SchemaException $e) { - echo "Error: " . $e->getMessage(); // User-friendly message - echo "Code: " . $e->getCode(); // Error code - echo "Property: " . $e->getProperty(); // Failed property (if available) - echo "Value: " . $e->getFailedValue(); // Value that failed (if available) -} -``` - -## Exception Types and Error Codes - -Different validation failures produce specific error codes: - -```php -$userSchema = Schema::object('user') - ->properties( - Schema::string('name') - ->minLength(2) - ->maxLength(50) - ->required(), - Schema::string('email') - ->format(SchemaFormat::Email) - ->required(), - Schema::integer('age') - ->minimum(0) - ->maximum(150) - ->required() - ); - -// Test different error scenarios -$testCases = [ - // Missing required field - ['name' => 'John'], - - // String too short - ['name' => 'J', 'email' => 'john@example.com', 'age' => 30], - - // Invalid format - ['name' => 'John', 'email' => 'invalid-email', 'age' => 30], - - // Type mismatch - ['name' => 'John', 'email' => 'john@example.com', 'age' => 'thirty'], - - // Value out of range - ['name' => 'John', 'email' => 'john@example.com', 'age' => -5] -]; - -foreach ($testCases as $index => $testData) { - try { - $userSchema->validate($testData); - echo "Test case {$index}: Valid\n"; - } catch (SchemaException $e) { - echo "Test case {$index}: {$e->getMessage()}\n"; - echo " Error code: {$e->getCode()}\n"; - echo " Failed property: " . ($e->getProperty() ?? 'unknown') . "\n\n"; - } -} -``` - -## Error Message Categories - -Schema validation errors fall into several categories: - -### Type Validation Errors - -```php -$typeSchema = Schema::string('name'); - -try { - $typeSchema->validate(123); // Number instead of string -} catch (SchemaException $e) { - echo $e->getMessage(); // "Value must be a string" - echo $e->getCode(); // Type validation error code -} -``` - -### Format Validation Errors - -```php -$emailSchema = Schema::string('email')->format(SchemaFormat::Email); - -try { - $emailSchema->validate('not-an-email'); -} catch (SchemaException $e) { - echo $e->getMessage(); // "Value must match the 'email' format" -} -``` - -### Constraint Validation Errors - -```php -$lengthSchema = Schema::string('username') - ->minLength(3) - ->maxLength(20); - -try { - $lengthSchema->validate('ab'); // Too short -} catch (SchemaException $e) { - echo $e->getMessage(); // "String must have minimum length of 3" -} - -try { - $lengthSchema->validate('very_long_username_that_exceeds_limit'); -} catch (SchemaException $e) { - echo $e->getMessage(); // "String must have maximum length of 20" -} -``` - -### Required Field Errors - -```php -$requiredSchema = Schema::object('user') - ->properties( - Schema::string('name')->required(), - Schema::string('email')->required() - ); - -try { - $requiredSchema->validate(['name' => 'John']); // Missing email -} catch (SchemaException $e) { - echo $e->getMessage(); // "Property 'email' is required" - echo $e->getProperty(); // "email" -} -``` - -## Custom Error Messages - -Create user-friendly error messages for different contexts: - -```php -class UserFriendlyErrorHandler -{ - private array $messageMap = [ - // Type errors - 'must be a string' => 'Please enter text for this field', - 'must be an integer' => 'Please enter a whole number', - 'must be a number' => 'Please enter a valid number', - 'must be a boolean' => 'Please select yes or no', - - // Format errors - 'must match the \'email\' format' => 'Please enter a valid email address', - 'must match the \'uri\' format' => 'Please enter a valid URL', - 'must match the \'date\' format' => 'Please enter a valid date (YYYY-MM-DD)', - - // Constraint errors - 'must have minimum length' => 'This field is too short', - 'must have maximum length' => 'This field is too long', - 'must be greater than or equal to' => 'Value is too small', - 'must be less than or equal to' => 'Value is too large', - - // Required field errors - 'is required' => 'This field is required', - ]; - - public function getFriendlyMessage(SchemaException $e): string - { - $originalMessage = $e->getMessage(); - - foreach ($this->messageMap as $pattern => $friendlyMessage) { - if (strpos($originalMessage, $pattern) !== false) { - return $friendlyMessage; - } - } - - return $originalMessage; // Fallback to original message - } - - public function getFieldSpecificMessage(SchemaException $e): string - { - $property = $e->getProperty(); - $friendlyMessage = $this->getFriendlyMessage($e); - - if ($property) { - $fieldName = ucfirst(str_replace('_', ' ', $property)); - return "{$fieldName}: {$friendlyMessage}"; - } - - return $friendlyMessage; - } -} - -// Usage -$errorHandler = new UserFriendlyErrorHandler(); - -try { - $userSchema->validate(['name' => 'J', 'email' => 'invalid']); -} catch (SchemaException $e) { - echo "Technical: " . $e->getMessage() . "\n"; - echo "User-friendly: " . $errorHandler->getFriendlyMessage($e) . "\n"; - echo "Field-specific: " . $errorHandler->getFieldSpecificMessage($e) . "\n"; -} -``` - -## Validation Result Objects - -Create structured validation results for better error handling: - -```php -class ValidationResult -{ - public function __construct( - public bool $isValid, - public array $errors = [], - public ?array $data = null - ) {} - - public function hasErrors(): bool - { - return !empty($this->errors); - } - - public function getFirstError(): ?string - { - return $this->errors[0] ?? null; - } - - public function getErrorsForField(string $field): array - { - return array_filter($this->errors, fn($error) => - isset($error['field']) && $error['field'] === $field - ); - } -} - -class ValidatorWithResults -{ - public function validate(Schema $schema, mixed $data): ValidationResult - { - if ($schema->isValid($data)) { - return new ValidationResult(true, [], $data); - } - - $errors = []; - try { - $schema->validate($data); - } catch (SchemaException $e) { - $errors[] = [ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'field' => $e->getProperty(), - 'value' => $e->getFailedValue() - ]; - } - - return new ValidationResult(false, $errors, $data); - } - - public function validateMultiple(Schema $schema, array $items): array - { - $results = []; - - foreach ($items as $index => $item) { - $results[$index] = $this->validate($schema, $item); - } - - return $results; - } -} - -// Usage -$validator = new ValidatorWithResults(); -$userSchema = Schema::object('user') - ->properties( - Schema::string('name')->minLength(2)->required(), - Schema::string('email')->format(SchemaFormat::Email)->required() - ); - -$result = $validator->validate($userSchema, ['name' => 'J']); - -if (!$result->isValid) { - echo "Validation failed:\n"; - foreach ($result->errors as $error) { - echo "- {$error['message']}\n"; - } -} -``` - -## Nested Object Error Handling - -Handle errors in complex nested structures: - -```php -class NestedErrorHandler -{ - public function validateWithPath(Schema $schema, mixed $data, string $path = ''): array - { - $errors = []; - - try { - $schema->validate($data); - } catch (SchemaException $e) { - $fieldPath = $path; - if ($e->getProperty()) { - $fieldPath .= ($fieldPath ? '.' : '') . $e->getProperty(); - } - - $errors[] = [ - 'path' => $fieldPath, - 'message' => $e->getMessage(), - 'value' => $e->getFailedValue() - ]; - } - - return $errors; - } - - public function validateObject(Schema $schema, array $data, string $basePath = ''): array - { - $allErrors = []; - - // First validate the entire object - $errors = $this->validateWithPath($schema, $data, $basePath); - $allErrors = array_merge($allErrors, $errors); - - // If the object validation passes, validate individual properties - if (empty($errors) && is_array($data)) { - $schemaArray = $schema->toArray(); - - if (isset($schemaArray['properties'])) { - foreach ($schemaArray['properties'] as $property => $propSchema) { - if (isset($data[$property])) { - $propertyPath = $basePath . ($basePath ? '.' : '') . $property; - $propertySchema = Schema::fromJson($propSchema); - - $propertyErrors = $this->validateWithPath( - $propertySchema, - $data[$property], - $propertyPath - ); - - $allErrors = array_merge($allErrors, $propertyErrors); - } - } - } - } - - return $allErrors; - } -} - -// Complex nested schema -$orderSchema = Schema::object('order') - ->properties( - Schema::string('id')->required(), - Schema::object('customer') - ->properties( - Schema::string('name')->minLength(2)->required(), - Schema::string('email')->format(SchemaFormat::Email)->required(), - Schema::object('address') - ->properties( - Schema::string('street')->required(), - Schema::string('city')->required(), - Schema::string('postal_code') - ->pattern('^\d{5}$') - ->required() - ) - ->required() - ) - ->required(), - Schema::array('items') - ->items( - Schema::object() - ->properties( - Schema::string('sku')->required(), - Schema::integer('quantity')->minimum(1)->required() - ) - ) - ->minItems(1) - ->required() - ); - -$nestedHandler = new NestedErrorHandler(); - -$invalidOrder = [ - 'id' => 'ORD-123', - 'customer' => [ - 'name' => 'J', // Too short - 'email' => 'invalid-email', // Invalid format - 'address' => [ - 'street' => '123 Main St', - // Missing city - 'postal_code' => '1234' // Invalid format - ] - ], - 'items' => [ - [ - 'sku' => 'ITEM-001', - 'quantity' => 0 // Below minimum - ] - ] -]; - -$errors = $nestedHandler->validateObject($orderSchema, $invalidOrder); - -foreach ($errors as $error) { - echo "Error at {$error['path']}: {$error['message']}\n"; -} -``` - -## Error Recovery and Suggestions - -Provide helpful suggestions for common validation errors: - -```php -class ErrorRecoveryHelper -{ - public function suggestCorrections(SchemaException $e): array - { - $suggestions = []; - $message = $e->getMessage(); - $value = $e->getFailedValue(); - - // Email format suggestions - if (strpos($message, 'email') !== false && is_string($value)) { - if (!strpos($value, '@')) { - $suggestions[] = "Add an '@' symbol to your email address"; - } elseif (!strpos($value, '.')) { - $suggestions[] = "Add a domain extension like '.com' to your email"; - } - } - - // String length suggestions - if (strpos($message, 'minimum length') !== false) { - preg_match('/minimum length of (\d+)/', $message, $matches); - if ($matches) { - $required = (int)$matches[1]; - $current = is_string($value) ? strlen($value) : 0; - $needed = $required - $current; - $suggestions[] = "Add {$needed} more character" . ($needed > 1 ? 's' : ''); - } - } - - if (strpos($message, 'maximum length') !== false) { - preg_match('/maximum length of (\d+)/', $message, $matches); - if ($matches) { - $allowed = (int)$matches[1]; - $current = is_string($value) ? strlen($value) : 0; - $excess = $current - $allowed; - $suggestions[] = "Remove {$excess} character" . ($excess > 1 ? 's' : ''); - } - } - - // Numeric range suggestions - if (strpos($message, 'greater than or equal to') !== false) { - preg_match('/greater than or equal to ([\d.]+)/', $message, $matches); - if ($matches) { - $minimum = $matches[1]; - $suggestions[] = "Enter a value of {$minimum} or higher"; - } - } - - // Enum suggestions - if (strpos($message, 'must be one of') !== false) { - $suggestions[] = "Choose from the available options"; - } - - return $suggestions; - } - - public function getDetailedError(SchemaException $e): array - { - return [ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'field' => $e->getProperty(), - 'value' => $e->getFailedValue(), - 'suggestions' => $this->suggestCorrections($e) - ]; - } -} - -// Usage -$recoveryHelper = new ErrorRecoveryHelper(); - -try { - $emailSchema = Schema::string('email')->format(SchemaFormat::Email); - $emailSchema->validate('userexample.com'); // Missing @ -} catch (SchemaException $e) { - $detailedError = $recoveryHelper->getDetailedError($e); - - echo "Error: {$detailedError['message']}\n"; - if (!empty($detailedError['suggestions'])) { - echo "Suggestions:\n"; - foreach ($detailedError['suggestions'] as $suggestion) { - echo "- {$suggestion}\n"; - } - } -} -``` - -## Bulk Validation Error Handling - -Handle errors when validating multiple items: - -```php -class BulkValidator -{ - public function validateBatch(Schema $schema, array $items): array - { - $results = []; - - foreach ($items as $index => $item) { - try { - $schema->validate($item); - $results[$index] = [ - 'valid' => true, - 'data' => $item, - 'errors' => [] - ]; - } catch (SchemaException $e) { - $results[$index] = [ - 'valid' => false, - 'data' => $item, - 'errors' => [ - [ - 'message' => $e->getMessage(), - 'field' => $e->getProperty(), - 'code' => $e->getCode() - ] - ] - ]; - } - } - - return $results; - } - - public function getValidItems(array $batchResults): array - { - $validItems = []; - - foreach ($batchResults as $index => $result) { - if ($result['valid']) { - $validItems[$index] = $result['data']; - } - } - - return $validItems; - } - - public function getErrorSummary(array $batchResults): array - { - $summary = [ - 'total' => count($batchResults), - 'valid' => 0, - 'invalid' => 0, - 'error_types' => [] - ]; - - foreach ($batchResults as $result) { - if ($result['valid']) { - $summary['valid']++; - } else { - $summary['invalid']++; - - foreach ($result['errors'] as $error) { - $errorType = $error['message']; - $summary['error_types'][$errorType] = - ($summary['error_types'][$errorType] ?? 0) + 1; - } - } - } - - return $summary; - } -} - -// Usage -$bulkValidator = new BulkValidator(); -$userSchema = Schema::object('user') - ->properties( - Schema::string('name')->minLength(2)->required(), - Schema::string('email')->format(SchemaFormat::Email)->required() - ); - -$users = [ - ['name' => 'John Doe', 'email' => 'john@example.com'], // Valid - ['name' => 'J', 'email' => 'jane@example.com'], // Invalid name - ['name' => 'Bob Smith', 'email' => 'invalid-email'], // Invalid email - ['name' => 'Alice Johnson', 'email' => 'alice@example.com'] // Valid -]; - -$results = $bulkValidator->validateBatch($userSchema, $users); -$validUsers = $bulkValidator->getValidItems($results); -$summary = $bulkValidator->getErrorSummary($results); - -echo "Validation Summary:\n"; -echo "Total: {$summary['total']}\n"; -echo "Valid: {$summary['valid']}\n"; -echo "Invalid: {$summary['invalid']}\n"; - -if (!empty($summary['error_types'])) { - echo "\nError Types:\n"; - foreach ($summary['error_types'] as $errorType => $count) { - echo "- {$errorType}: {$count} occurrence(s)\n"; - } -} -``` - -## Best Practices - -### 1. Use Specific Error Messages - -```php -// Good: Specific validation rules with clear error context -$passwordSchema = Schema::string('password') - ->minLength(8) - ->pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*\d)') - ->description('Password must be at least 8 characters with uppercase, lowercase, and number'); - -// Provide context in error handling -try { - $passwordSchema->validate($password); -} catch (SchemaException $e) { - $errorContext = [ - 'field' => 'password', - 'message' => $e->getMessage(), - 'requirements' => 'At least 8 characters with uppercase, lowercase, and number' - ]; -} -``` - -### 2. Log Validation Errors - -```php -class ValidatorWithLogging -{ - private LoggerInterface $logger; - - public function validateWithLogging(Schema $schema, mixed $data, array $context = []): bool - { - try { - $schema->validate($data); - return true; - } catch (SchemaException $e) { - $this->logger->warning('Schema validation failed', [ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'field' => $e->getProperty(), - 'context' => $context - ]); - - return false; - } - } -} -``` - -### 3. Graceful Degradation - -```php -function processUserDataSafely(array $userData): array -{ - $userSchema = Schema::object('user') - ->properties( - Schema::string('name')->required(), - Schema::string('email')->format(SchemaFormat::Email)->required() - ); - - try { - $userSchema->validate($userData); - return ['status' => 'success', 'data' => $userData]; - } catch (SchemaException $e) { - // Log error but continue with partial processing - error_log("User data validation failed: " . $e->getMessage()); - - // Return what we can process safely - $safeData = []; - if (isset($userData['name']) && is_string($userData['name'])) { - $safeData['name'] = $userData['name']; - } - - return [ - 'status' => 'partial', - 'data' => $safeData, - 'errors' => [$e->getMessage()] - ]; - } -} -``` - -## Common Use Cases - - - - Provide user-friendly error messages and suggestions for form input validation. - - - Structure detailed error responses for API endpoints with validation failures. - - - Handle validation errors during bulk data import with detailed error reporting. - - - Provide helpful error messages for application configuration validation. - - - -## Next Steps - - - - Learn about version-specific validation features and compatibility - - - Master the core validation methods and techniques - - diff --git a/docs/json-schema/validation/version-features.mdx b/docs/json-schema/validation/version-features.mdx deleted file mode 100644 index ee09d65..0000000 --- a/docs/json-schema/validation/version-features.mdx +++ /dev/null @@ -1,636 +0,0 @@ ---- -title: Version Feature Validation -description: 'Automatic validation of JSON Schema version compatibility and feature support' -icon: 'ticket-check' ---- - -The package automatically validates that schema features are compatible with the specified JSON Schema version, preventing runtime errors and ensuring schema portability across different validators. - -## Automatic Feature Validation - -When you use features that require specific JSON Schema versions, the package automatically validates compatibility: - -```php -use Cortex\JsonSchema\Schema; -use Cortex\JsonSchema\Enums\SchemaVersion; -use Cortex\JsonSchema\Exceptions\SchemaException; - -// ✅ This works - deprecated is supported in Draft 2019-09+ -$modernSchema = Schema::string('legacy_field', SchemaVersion::Draft_2019_09) - ->deprecated() - ->comment('Use new_field instead'); - -// ❌ This throws an exception - deprecated requires Draft 2019-09+ -try { - $invalidSchema = Schema::string('legacy_field', SchemaVersion::Draft_07) - ->deprecated(); // SchemaException thrown here -} catch (SchemaException $e) { - echo $e->getMessage(); - // "Feature 'Property deprecation annotation' is not supported in Draft 7. - // Minimum version required: Draft 2019-09." -} -``` - -## Feature Support Matrix - -Understanding which features are available in each JSON Schema version: - -### Core Validation (All Versions) - -```php -// These features work in all supported versions -$basicSchema = Schema::object('basic', SchemaVersion::Draft_07) - ->properties( - Schema::string('name') - ->minLength(2) - ->maxLength(100) - ->pattern('^[A-Za-z\s]+$') - ->required(), - Schema::integer('age') - ->minimum(0) - ->maximum(150) - ->required(), - Schema::array('tags') - ->items(Schema::string()) - ->minItems(1) - ->maxItems(10) - ->uniqueItems(true) - ) - ->additionalProperties(false); -``` - -### Conditional Logic (Draft-07+) - -```php -// if/then/else works in Draft-07 and later -$conditionalSchema = Schema::object('user', SchemaVersion::Draft_07) - ->properties( - Schema::string('type')->enum(['personal', 'business']), - Schema::string('company_name'), - Schema::string('tax_id') - ) - ->if( - Schema::object()->properties( - Schema::string('type')->const('business') - ) - ) - ->then( - Schema::object()->properties( - Schema::string('company_name')->required(), - Schema::string('tax_id')->required() - ) - ); -``` - -### Draft 2019-09 Features - -```php -// Advanced features requiring Draft 2019-09+ -$modernSchema = Schema::object('modern_user', SchemaVersion::Draft_2019_09) - ->properties( - Schema::string('name')->required(), - Schema::string('email')->required(), - - // Deprecated annotation - Schema::string('old_email') - ->deprecated() - ->comment('Use email field instead'), - - // Array contains validation - Schema::array('roles') - ->items(Schema::string()) - ->minContains(1) // At least one role required - ->maxContains(5), // Maximum 5 roles - - // Extended format support - Schema::string('id') - ->format(SchemaFormat::Uuid), - - Schema::string('session_duration') - ->format(SchemaFormat::Duration) - ) - // Unevaluated properties - ->unevaluatedProperties(false) - - // Dependent schemas - ->dependentSchema('old_email', - Schema::object() - ->if(Schema::object()->properties( - Schema::string('old_email')->minLength(1) - )) - ->then(Schema::object() - ->properties( - Schema::string('migration_date') - ->format(SchemaFormat::Date) - ->required() - ) - ) - ); -``` - -### Draft 2020-12 Features - -```php -// Cutting-edge features in Draft 2020-12 -$latestSchema = Schema::array('tuple_array', SchemaVersion::Draft_2020_12) - // Prefix items for tuple validation - ->prefixItems([ - Schema::string()->description('Name'), - Schema::integer()->minimum(0)->description('Age'), - Schema::boolean()->description('Active status') - ]) - // Additional items after prefix - ->items(Schema::string()->description('Additional info')) - // Unevaluated items control - ->unevaluatedItems(false); -``` - -## Version Detection and Error Messages - -The package provides clear error messages when incompatible features are used: - -```php -function demonstrateVersionErrors() -{ - $examples = [ - 'Deprecated annotation' => function() { - return Schema::string('field', SchemaVersion::Draft_07) - ->deprecated(); - }, - - 'Min/Max contains' => function() { - return Schema::array('items', SchemaVersion::Draft_07) - ->minContains(1); - }, - - 'UUID format' => function() { - return Schema::string('id', SchemaVersion::Draft_07) - ->format(SchemaFormat::Uuid); - }, - - 'Unevaluated properties' => function() { - return Schema::object('strict', SchemaVersion::Draft_07) - ->unevaluatedProperties(false); - }, - - 'Dependent schemas' => function() { - return Schema::object('conditional', SchemaVersion::Draft_07) - ->dependentSchema('field', Schema::object()); - } - ]; - - foreach ($examples as $featureName => $schemaBuilder) { - try { - $schemaBuilder(); - echo "✅ {$featureName}: Compatible\n"; - } catch (SchemaException $e) { - echo "❌ {$featureName}: {$e->getMessage()}\n"; - } - } -} - -demonstrateVersionErrors(); -``` - -## Feature Detection - -Check feature support programmatically: - -```php -use Cortex\JsonSchema\Enums\SchemaFeature; - -/** - * Check if a feature is supported in a specific version - */ -function checkFeatureSupport(SchemaVersion $version, SchemaFeature $feature): bool -{ - return $version->supports($feature); -} - -/** - * Get minimum version required for a feature - */ -function getMinimumVersion(SchemaFeature $feature): SchemaVersion -{ - return $feature->getMinimumVersion(); -} - -// Feature compatibility checking -$features = [ - SchemaFeature::Deprecated, - SchemaFeature::MinContains, - SchemaFeature::MaxContains, - SchemaFeature::UnevaluatedProperties, - SchemaFeature::DependentSchemas, - SchemaFeature::PrefixItems -]; - -foreach ($features as $feature) { - $minVersion = getMinimumVersion($feature); - $description = $feature->getDescription(); - - echo "Feature: {$description}\n"; - echo "Minimum version: {$minVersion->name}\n"; - echo "Supported in Draft-07: " . - (checkFeatureSupport(SchemaVersion::Draft_07, $feature) ? 'Yes' : 'No') . "\n"; - echo "Supported in Draft 2019-09: " . - (checkFeatureSupport(SchemaVersion::Draft_2019_09, $feature) ? 'Yes' : 'No') . "\n"; - echo "Supported in Draft 2020-12: " . - (checkFeatureSupport(SchemaVersion::Draft_2020_12, $feature) ? 'Yes' : 'No') . "\n\n"; -} -``` - -## Schema Migration Between Versions - -Migrate schemas to take advantage of newer features: - -```php -class SchemaMigrator -{ - public function upgradeToDraft201909(Schema $draft07Schema): Schema - { - $schemaArray = $draft07Schema->toArray(); - - // Update schema version - $schemaArray['$schema'] = 'https://json-schema.org/draft/2019-09/schema'; - - // Convert definitions to $defs - if (isset($schemaArray['definitions'])) { - $schemaArray['$defs'] = $schemaArray['definitions']; - unset($schemaArray['definitions']); - - // Update $ref paths - $schemaArray = $this->updateRefPaths($schemaArray); - } - - return Schema::fromJson($schemaArray, SchemaVersion::Draft_2019_09); - } - - public function addModernFeatures(Schema $basicSchema): Schema - { - $schemaArray = $basicSchema->toArray(); - - // Ensure we're using a modern version - if (!isset($schemaArray['$schema']) || - strpos($schemaArray['$schema'], '2019-09') === false) { - $schemaArray['$schema'] = 'https://json-schema.org/draft/2019-09/schema'; - } - - // Add modern validation features - if ($schemaArray['type'] === 'object') { - // Add strict property validation - $schemaArray['unevaluatedProperties'] = false; - - // Add examples for better documentation - if (isset($schemaArray['properties'])) { - foreach ($schemaArray['properties'] as $prop => &$propSchema) { - if ($propSchema['type'] === 'string' && !isset($propSchema['examples'])) { - $propSchema['examples'] = ["example_{$prop}"]; - } - } - } - } - - return Schema::fromJson($schemaArray, SchemaVersion::Draft_2019_09); - } - - private function updateRefPaths(array $schema): array - { - foreach ($schema as $key => &$value) { - if ($key === '$ref' && is_string($value)) { - $value = str_replace('#/definitions/', '#/$defs/', $value); - } elseif (is_array($value)) { - $value = $this->updateRefPaths($value); - } - } - - return $schema; - } -} - -// Usage example -$migrator = new SchemaMigrator(); - -// Original Draft-07 schema -$originalSchema = Schema::object('user', SchemaVersion::Draft_07) - ->properties( - Schema::string('name')->required(), - Schema::string('email')->required() - ) - ->addDefinition('address', - Schema::object() - ->properties( - Schema::string('street')->required(), - Schema::string('city')->required() - ) - ); - -// Upgrade to take advantage of Draft 2019-09 features -$modernSchema = $migrator->upgradeToDraft201909($originalSchema); -$enhancedSchema = $migrator->addModernFeatures($modernSchema); - -// Now we can use modern features -$enhancedSchema = $enhancedSchema - ->unevaluatedProperties(false) // This now works - ->properties( - Schema::string('legacy_field') - ->deprecated() // This now works too - ); -``` - -## Conditional Feature Usage - -Use features conditionally based on version support: - -```php -class VersionAwareSchemaBuilder -{ - public function buildUserSchema(SchemaVersion $version): Schema - { - $schema = Schema::object('user', $version) - ->properties( - Schema::string('name')->required(), - Schema::string('email')->required() - ); - - // Add version-specific features - if ($version->supports(SchemaFeature::Deprecated)) { - $schema = $schema->properties( - Schema::string('old_email') - ->deprecated() - ->comment('Use email field instead') - ); - } - - if ($version->supports(SchemaFeature::UnevaluatedProperties)) { - $schema = $schema->unevaluatedProperties(false); - } - - if ($version->supports(SchemaFeature::MinContains)) { - $schema = $schema->properties( - Schema::array('roles') - ->items(Schema::string()) - ->minContains(1) - ->maxContains(5) - ); - } else { - // Fallback for older versions - $schema = $schema->properties( - Schema::array('roles') - ->items(Schema::string()) - ->minItems(1) - ->maxItems(5) - ); - } - - return $schema; - } - - public function buildWithBestFeatures(SchemaVersion $targetVersion): Schema - { - // Use the most advanced features available - $availableFeatures = [ - SchemaFeature::Deprecated, - SchemaFeature::UnevaluatedProperties, - SchemaFeature::MinContains, - SchemaFeature::DependentSchemas - ]; - - $schema = Schema::object('advanced_user', $targetVersion) - ->properties( - Schema::string('username')->required(), - Schema::string('email')->required() - ); - - foreach ($availableFeatures as $feature) { - if ($targetVersion->supports($feature)) { - $schema = $this->addFeatureToSchema($schema, $feature); - } - } - - return $schema; - } - - private function addFeatureToSchema(Schema $schema, SchemaFeature $feature): Schema - { - switch ($feature) { - case SchemaFeature::Deprecated: - return $schema->properties( - Schema::string('legacy_username') - ->deprecated() - ->comment('Use username instead') - ); - - case SchemaFeature::UnevaluatedProperties: - return $schema->unevaluatedProperties(false); - - case SchemaFeature::MinContains: - return $schema->properties( - Schema::array('permissions') - ->items(Schema::string()) - ->minContains(1) - ); - - case SchemaFeature::DependentSchemas: - return $schema->dependentSchema('legacy_username', - Schema::object() - ->properties( - Schema::string('migration_date') - ->format(SchemaFormat::Date) - ->required() - ) - ); - - default: - return $schema; - } - } -} - -// Usage -$builder = new VersionAwareSchemaBuilder(); - -// Build schemas for different target environments -$draft07Schema = $builder->buildUserSchema(SchemaVersion::Draft_07); -$modernSchema = $builder->buildUserSchema(SchemaVersion::Draft_2019_09); -$latestSchema = $builder->buildUserSchema(SchemaVersion::Draft_2020_12); - -// Each schema uses the best features available for its version -``` - -## Validation Environment Detection - -Detect and adapt to different validation environments: - -```php -class EnvironmentAwareValidator -{ - private SchemaVersion $supportedVersion; - - public function __construct() - { - $this->supportedVersion = $this->detectEnvironmentVersion(); - } - - private function detectEnvironmentVersion(): SchemaVersion - { - // In a real application, you might: - // - Check client capabilities - // - Read from configuration - // - Test feature support - - // For demo, assume based on PHP version or configuration - $configVersion = $_ENV['JSON_SCHEMA_VERSION'] ?? 'draft-2020-12'; - - return match($configVersion) { - 'draft-07' => SchemaVersion::Draft_07, - 'draft-2019-09' => SchemaVersion::Draft_2019_09, - default => SchemaVersion::Draft_2020_12 - }; - } - - public function createCompatibleSchema(array $requirements): Schema - { - $schema = Schema::object('dynamic', $this->supportedVersion); - - foreach ($requirements as $field => $config) { - $fieldSchema = $this->createFieldSchema($config); - $schema = $schema->properties($fieldSchema->propertyName($field)); - } - - // Add version-appropriate constraints - if ($this->supportedVersion->supports(SchemaFeature::UnevaluatedProperties)) { - $schema = $schema->unevaluatedProperties(false); - } else { - $schema = $schema->additionalProperties(false); - } - - return $schema; - } - - private function createFieldSchema(array $config): Schema - { - $type = $config['type'] ?? 'string'; - $fieldSchema = match($type) { - 'string' => Schema::string(), - 'integer' => Schema::integer(), - 'number' => Schema::number(), - 'boolean' => Schema::boolean(), - 'array' => Schema::array(), - default => Schema::string() - }; - - // Apply constraints based on version support - if (isset($config['deprecated']) && - $this->supportedVersion->supports(SchemaFeature::Deprecated)) { - $fieldSchema = $fieldSchema->deprecated(); - } - - if (isset($config['format'])) { - $format = SchemaFormat::from($config['format']); - if ($this->isFormatSupported($format)) { - $fieldSchema = $fieldSchema->format($format); - } - } - - return $fieldSchema; - } - - private function isFormatSupported(SchemaFormat $format): bool - { - $modernFormats = [SchemaFormat::Uuid, SchemaFormat::Duration]; - - if (in_array($format, $modernFormats)) { - return $this->supportedVersion->supports(SchemaFeature::ExtendedFormats); - } - - return true; // Basic formats supported in all versions - } -} -``` - -## Best Practices - -### 1. Use Version Detection - -```php -// Good: Check version support before using features -if (SchemaVersion::Draft_2019_09->supports(SchemaFeature::Deprecated)) { - $schema = $schema->deprecated(); -} - -// Avoid: Assuming feature support -$schema = $schema->deprecated(); // May fail in older versions -``` - -### 2. Provide Fallbacks - -```php -// Good: Provide fallbacks for unsupported features -function addStrictValidation(Schema $schema, SchemaVersion $version): Schema -{ - if ($version->supports(SchemaFeature::UnevaluatedProperties)) { - return $schema->unevaluatedProperties(false); - } else { - return $schema->additionalProperties(false); - } -} -``` - -### 3. Document Version Requirements - -```php -/** - * Create a modern user schema with advanced validation - * - * @param SchemaVersion $version Must be Draft 2019-09 or later for full features - * @throws SchemaException If version doesn't support required features - */ -function createAdvancedUserSchema(SchemaVersion $version): Schema -{ - if (!$version->supports(SchemaFeature::UnevaluatedProperties)) { - throw new SchemaException( - "Advanced user schema requires Draft 2019-09 or later" - ); - } - - return Schema::object('advanced_user', $version) - ->unevaluatedProperties(false); -} -``` - -## Common Use Cases - - - - Adapt schema validation based on API version and client capabilities. - - - Start with basic validation and add advanced features when supported. - - - Ensure schema compatibility across different JSON Schema validators. - - - Migrate schemas to newer versions while maintaining backward compatibility. - - - -## Next Steps - - - - Learn more about JSON Schema version differences and capabilities - - - Explore the complete API reference and method documentation - - diff --git a/src/Contracts/JsonSchema.php b/src/Contracts/JsonSchema.php index 6b8c37d..3a2d2c8 100644 --- a/src/Contracts/JsonSchema.php +++ b/src/Contracts/JsonSchema.php @@ -18,6 +18,11 @@ public function title(string $title): static; */ public function getTitle(): ?string; + /** + * Determine if the schema has a title + */ + public function hasTitle(): bool; + /** * Set the description */ diff --git a/src/Types/Concerns/HasProperties.php b/src/Types/Concerns/HasProperties.php index a8c9b83..1a3d82c 100644 --- a/src/Types/Concerns/HasProperties.php +++ b/src/Types/Concerns/HasProperties.php @@ -254,7 +254,11 @@ public function hasRequiredProperties(): bool public function requireAll(): static { foreach ($this->properties as $property) { - $property->required(); + $title = $property->getTitle(); + + if ($title !== null) { + $this->requiredProperties[] = $title; + } } return $this; diff --git a/src/Types/Concerns/HasTitle.php b/src/Types/Concerns/HasTitle.php index 21402ce..55f13ce 100644 --- a/src/Types/Concerns/HasTitle.php +++ b/src/Types/Concerns/HasTitle.php @@ -27,6 +27,14 @@ public function getTitle(): ?string return $this->title; } + /** + * Determine if the schema has a title + */ + public function hasTitle(): bool + { + return $this->title !== null; + } + /** * Add title to schema array *