From fe888acc3dec6e87e63bb399393893ba146bbab1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 21:07:30 +0000 Subject: [PATCH] Fix JSON schema: snake_case title and make all fields required The schema title was using the raw template name (e.g. "Document") instead of snake_case (e.g. "document"), causing errors with OpenAI/Anthropic APIs. Additionally, all fields are now always included in the required array, as strict mode requires every property to be listed as required. https://claude.ai/code/session_01VMGZKwtTLfYK7r3pL2514T --- src/Ephemeral/EphemeralSection.php | 10 ++-------- src/Ephemeral/EphemeralTemplate.php | 3 ++- src/Models/Section.php | 10 ++-------- src/Models/Template.php | 3 ++- tests/Feature/EphemeralSectionTest.php | 4 ++-- tests/Feature/EphemeralTemplateTest.php | 2 +- tests/Feature/SchematicServiceTest.php | 2 +- tests/Feature/SectionTest.php | 2 +- tests/Feature/TemplateTest.php | 2 +- 9 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Ephemeral/EphemeralSection.php b/src/Ephemeral/EphemeralSection.php index 5c95b19..16dcf94 100644 --- a/src/Ephemeral/EphemeralSection.php +++ b/src/Ephemeral/EphemeralSection.php @@ -59,21 +59,15 @@ public function toJsonSchema(): array foreach ($this->fieldDefinitions() as $field) { $properties[$field->name] = $field->toJsonSchemaProperty(); - - if ($field->required) { - $required[] = $field->name; - } + $required[] = $field->name; } $schema = [ 'type' => 'object', 'properties' => $properties, + 'required' => $required, ]; - if ($required !== []) { - $schema['required'] = $required; - } - if ($this->description) { $schema['description'] = $this->description; } diff --git a/src/Ephemeral/EphemeralTemplate.php b/src/Ephemeral/EphemeralTemplate.php index 04aef96..02b1bde 100644 --- a/src/Ephemeral/EphemeralTemplate.php +++ b/src/Ephemeral/EphemeralTemplate.php @@ -3,6 +3,7 @@ namespace Yannelli\Schematic\Ephemeral; use Illuminate\Support\Collection; +use Illuminate\Support\Str; use Yannelli\Schematic\Compiler; use Yannelli\Schematic\Contracts\Renderable; use Yannelli\Schematic\Contracts\SchemaGeneratable; @@ -158,7 +159,7 @@ public function toJsonSchemaDocument(): array { return [ '$schema' => config('schematic.schema.draft'), - 'title' => $this->name, + 'title' => Str::snake($this->name), ...$this->toJsonSchema(), ]; } diff --git a/src/Models/Section.php b/src/Models/Section.php index cb0a833..60ec419 100644 --- a/src/Models/Section.php +++ b/src/Models/Section.php @@ -64,21 +64,15 @@ public function toJsonSchema(): array foreach ($this->field_definitions as $field) { $properties[$field->name] = $field->toJsonSchemaProperty(); - - if ($field->required) { - $required[] = $field->name; - } + $required[] = $field->name; } $schema = [ 'type' => 'object', 'properties' => $properties, + 'required' => $required, ]; - if ($required !== []) { - $schema['required'] = $required; - } - if ($this->description) { $schema['description'] = $this->description; } diff --git a/src/Models/Template.php b/src/Models/Template.php index d187616..973f33b 100644 --- a/src/Models/Template.php +++ b/src/Models/Template.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Str; use Yannelli\Schematic\Compiler; use Yannelli\Schematic\Contracts\Renderable; use Yannelli\Schematic\Contracts\SchemaGeneratable; @@ -150,7 +151,7 @@ public function toJsonSchemaDocument(): array { return [ '$schema' => config('schematic.schema.draft'), - 'title' => $this->name, + 'title' => Str::snake($this->name), ...$this->toJsonSchema(), ]; } diff --git a/tests/Feature/EphemeralSectionTest.php b/tests/Feature/EphemeralSectionTest.php index a920d4f..1fe00e8 100644 --- a/tests/Feature/EphemeralSectionTest.php +++ b/tests/Feature/EphemeralSectionTest.php @@ -56,7 +56,7 @@ ->and($schema['properties'])->toHaveKey('count') ->and($schema['properties']['title']['type'])->toBe('string') ->and($schema['properties']['count']['type'])->toBe('integer') - ->and($schema['required'])->toBe(['title']) + ->and($schema['required'])->toBe(['title', 'count']) ->and($schema['description'])->toBe('Test section'); }); @@ -86,7 +86,7 @@ $schema = $section->toJsonSchema(); expect($schema['properties']['notes']['type'])->toBe(['string', 'null']) - ->and($schema)->not->toHaveKey('required'); + ->and($schema['required'])->toBe(['notes']); }); // --------------------------------------------------------------- diff --git a/tests/Feature/EphemeralTemplateTest.php b/tests/Feature/EphemeralTemplateTest.php index 58067ba..c456ce8 100644 --- a/tests/Feature/EphemeralTemplateTest.php +++ b/tests/Feature/EphemeralTemplateTest.php @@ -142,7 +142,7 @@ $doc = $template->toJsonSchemaDocument(); expect($doc)->toHaveKey('$schema') - ->and($doc['title'])->toBe('Document') + ->and($doc['title'])->toBe('document') ->and($doc['type'])->toBe('object'); }); diff --git a/tests/Feature/SchematicServiceTest.php b/tests/Feature/SchematicServiceTest.php index e668460..eeaa4c0 100644 --- a/tests/Feature/SchematicServiceTest.php +++ b/tests/Feature/SchematicServiceTest.php @@ -94,7 +94,7 @@ $doc = $this->schematic->schemaDocument('doc-svc'); expect($doc)->toHaveKey('$schema') - ->and($doc['title'])->toBe('Document'); + ->and($doc['title'])->toBe('document'); }); // --------------------------------------------------------------- diff --git a/tests/Feature/SectionTest.php b/tests/Feature/SectionTest.php index cef968e..6676057 100644 --- a/tests/Feature/SectionTest.php +++ b/tests/Feature/SectionTest.php @@ -131,7 +131,7 @@ expect($schema['type'])->toBe('object') ->and($schema['properties'])->toHaveKey('title') ->and($schema['properties'])->toHaveKey('optional') - ->and($schema['required'])->toBe(['title']) + ->and($schema['required'])->toBe(['title', 'optional']) ->and($schema['additionalProperties'])->toBeFalse(); }); diff --git a/tests/Feature/TemplateTest.php b/tests/Feature/TemplateTest.php index 93fec47..1346f51 100644 --- a/tests/Feature/TemplateTest.php +++ b/tests/Feature/TemplateTest.php @@ -165,7 +165,7 @@ $doc = $template->toJsonSchemaDocument(); expect($doc['$schema'])->toBe('https://json-schema.org/draft/2020-12/schema') - ->and($doc['title'])->toBe('Document') + ->and($doc['title'])->toBe('document') ->and($doc['type'])->toBe('object'); });