Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 127 additions & 189 deletions README.md

Large diffs are not rendered by default.

16 changes: 0 additions & 16 deletions config/forerunner.php

This file was deleted.

1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ parameters:
level: 9
paths:
- src
- config
tmpDir: build/phpstan
2 changes: 2 additions & 0 deletions src/Commands/MakeStructCommand.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Blaspsoft\Forerunner\Commands;

use Illuminate\Console\GeneratorCommand;
Expand Down
6 changes: 4 additions & 2 deletions src/Facades/Schema.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<?php

declare(strict_types=1);

namespace Blaspsoft\Forerunner\Facades;

use Illuminate\Support\Facades\Facade;

/**
* @method static array<string, mixed> define(string $name, callable $callback)
* @method static \Blaspsoft\Forerunner\Schema\Struct define(string $name, string $description, callable $callback)
*
* @see \Blaspsoft\Forerunner\Schemas\Struct
* @see \Blaspsoft\Forerunner\Schema\Struct
*/
class Schema extends Facade
{
Expand Down
13 changes: 3 additions & 10 deletions src/ForerunnerServiceProvider.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?php

declare(strict_types=1);

namespace Blaspsoft\Forerunner;

use Blaspsoft\Forerunner\Commands\MakeStructCommand;
use Blaspsoft\Forerunner\Schemas\Struct;
use Blaspsoft\Forerunner\Schema\Struct;
use Illuminate\Support\ServiceProvider;

class ForerunnerServiceProvider extends ServiceProvider
Expand All @@ -13,11 +15,6 @@ class ForerunnerServiceProvider extends ServiceProvider
*/
public function register(): void
{
$this->mergeConfigFrom(
__DIR__.'/../config/forerunner.php',
'forerunner'
);

$this->app->singleton('forerunner.schema', function () {
return new class
{
Expand Down Expand Up @@ -46,10 +43,6 @@ public function __call(string $method, array $args): mixed
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../config/forerunner.php' => config_path('forerunner.php'),
], 'forerunner-config');

$this->commands([
MakeStructCommand::class,
]);
Expand Down
46 changes: 27 additions & 19 deletions src/Schemas/Builder.php → src/Schema/Property.php
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
<?php

namespace Blaspsoft\Forerunner\Schemas;
declare(strict_types=1);

class Builder
namespace Blaspsoft\Forerunner\Schema;

class Property
{
protected string $name;

/** @var array<string, PropertyBuilder> */
protected array $properties = [];

/** @var array<int, string> */
protected array $required = [];

protected ?string $description = null;

protected bool $additionalProperties = false;
Expand All @@ -20,6 +19,8 @@ class Builder

protected ?string $title = null;

protected bool $isStrict = false;

public function __construct(string $name)
{
$this->name = $name;
Expand Down Expand Up @@ -94,7 +95,7 @@ public function array(string $name, ?string $description = null): PropertyBuilde
*/
public function object(string $name, callable $callback, ?string $description = null): PropertyBuilder
{
$nestedBuilder = new Builder($name);
$nestedBuilder = new Property($name);
$callback($nestedBuilder);

$builder = new PropertyBuilder($name, 'object', $description);
Expand Down Expand Up @@ -226,6 +227,7 @@ public function additionalProperties(bool $allowed = true): self
*/
public function strict(): self
{
$this->isStrict = true;
$this->additionalProperties = false;

// Mark all properties as required
Expand All @@ -236,6 +238,14 @@ public function strict(): self
return $this;
}

/**
* Check if strict mode is enabled.
*/
public function isStrict(): bool
{
return $this->isStrict;
}

/**
* Set the JSON Schema version.
*/
Expand All @@ -252,21 +262,17 @@ public function schemaVersion(string $version = 'https://json-schema.org/draft/2
protected function addProperty(string $name, string $type, ?string $description): PropertyBuilder
{
$builder = new PropertyBuilder($name, $type, $description);

// In strict mode, all fields must be required, including those added after strict()
if ($this->isStrict) {
$builder->required();
}

$this->properties[$name] = $builder;

return $builder;
}

/**
* Mark a field as required.
*/
public function markRequired(string $name): void
{
if (! in_array($name, $this->required)) {
$this->required[] = $name;
}
}

/**
* Convert the builder to a JSON schema array.
*
Expand All @@ -291,16 +297,18 @@ public function toArray(): array
$schema['description'] = $this->description;
}

// Build required array from property states without mutating $this->required
$required = [];
foreach ($this->properties as $name => $builder) {
$schema['properties'][$name] = $builder->toArray();

if ($builder->isRequired()) {
$this->markRequired($name);
$required[] = $name;
}
}

if (! empty($this->required)) {
$schema['required'] = $this->required;
if (! empty($required)) {
$schema['required'] = $required;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$schema['additionalProperties'] = $this->additionalProperties;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

namespace Blaspsoft\Forerunner\Schemas;
declare(strict_types=1);

namespace Blaspsoft\Forerunner\Schema;

class PropertyBuilder
{
Expand All @@ -15,7 +17,7 @@ class PropertyBuilder
/** @var array<int, mixed>|null */
protected ?array $enum = null;

protected ?Builder $nestedBuilder = null;
protected ?Property $nestedBuilder = null;

/** @var array<string, mixed>|null */
protected ?array $items = null;
Expand Down Expand Up @@ -99,7 +101,7 @@ public function enum(array $values): self
public function items(string $type, ?callable $callback = null): self
{
if ($callback && $type === 'object') {
$nestedBuilder = new Builder($this->name.'_item');
$nestedBuilder = new Property($this->name.'_item');
$callback($nestedBuilder);
$this->items = $nestedBuilder->toArray();
} else {
Expand Down Expand Up @@ -232,7 +234,7 @@ public function nullable(bool $nullable = true): self
/**
* Set a nested builder for object types.
*/
public function setNestedBuilder(Builder $builder): void
public function setNestedBuilder(Property $builder): void
{
$this->nestedBuilder = $builder;
}
Expand Down
70 changes: 70 additions & 0 deletions src/Schema/Struct.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Blaspsoft\Forerunner\Schema;

class Struct implements \JsonSerializable
{
protected Property $builder;

protected string $name;

/** @var array<string, mixed>|null */
protected ?array $cache = null;

protected function __construct(string $name, Property $builder)
{
$this->name = $name;
$this->builder = $builder;
}

/**
* Define a new structure schema.
*/
public static function define(string $name, string $description, callable $callback): self
{
$builder = new Property($name);
$builder->description($description);

$callback($builder);

return new self($name, $builder);
}

/**
* Convert the schema to an array.
* If strict mode is enabled, wraps the schema in OpenAI's format.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
if ($this->cache === null) {
$builderArray = $this->builder->toArray();

// If strict mode is enabled, wrap in OpenAI's format
if ($this->builder->isStrict()) {
$this->cache = [
'name' => $this->name,
'strict' => true,
'schema' => $builderArray,
];
} else {
$this->cache = $builderArray;
}
}

return $this->cache;
}

/**
* Specify data which should be serialized to JSON.
*
* @return array<string, mixed>
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
}
Loading