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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,40 @@ $request = $decoder->decode(/** protobuf buffer here */, CreateUserRequest::clas
echo $request->name;
```

### Required fields

`Reflector::map()` maps missing fields using property defaults when they exist.
If a property is non-nullable and has no default value, it is treated as required.

When one or more required properties are missing, decoding fails with `Thesis\Protobuf\Reflection\Exception\MappingError`.
The exception contains all reasons in `->reasons` (each reason is typically `PropertyRequired`).

This behavior is aligned with other protobuf implementations/plugins: messages missing required fields are treated as invalid during decode.

A field is considered required when either:
- it is defined as `required` in `proto2`;
- it is defined in editions with `features.field_presence = LEGACY_REQUIRED`.

```php
use Thesis\Protobuf\Decoder;
use Thesis\Protobuf\Reflection;

$decoder = Decoder\Builder::buildDefault();

try {
$message = $decoder->decode($buffer, CreateUserRequest::class);
} catch (Reflection\Exception\MappingError $e) {
foreach ($e->reasons as $reason) {
if ($reason instanceof Reflection\Exception\PropertyRequired) {
echo $reason->class . "::$" . $reason->property . PHP_EOL;
}
}
}
```

Both `Encoder::encode()` and `Decoder::decode()` throw `Thesis\Protobuf\ProtobufException`.
Domain protobuf exceptions are preserved and are not wrapped into generic runtime errors.

### Unknown fields

When a protobuf message is decoded, it may contain fields that are not defined in the target class.
Expand Down
4 changes: 1 addition & 3 deletions src/Decoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Thesis\Protobuf;

use Thesis\Protobuf\Decoder\DecodingError;

/**
* @api
*/
Expand All @@ -15,7 +13,7 @@ interface Decoder
* @template T of object
* @param class-string<T> $classType
* @return T
* @throws DecodingError
* @throws ProtobufException
*/
public function decode(string $buffer, string $classType): object;
}
3 changes: 3 additions & 0 deletions src/Decoder/Internal/ReflectionDecoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Thesis\Protobuf\Decoder\Internal;

use Thesis\Protobuf\Decoder;
use Thesis\Protobuf\ProtobufException;
use Thesis\Protobuf\Reflection\Reflector;
use Thesis\Protobuf\Serializer;

Expand All @@ -29,6 +30,8 @@ public function decode(string $buffer, string $classType): object
),
$classType,
);
} catch (ProtobufException $e) {
throw $e;
} catch (\Throwable $e) {
throw new Decoder\DecodingError($e->getMessage(), (int) $e->getCode(), $e);
}
Expand Down
4 changes: 1 addition & 3 deletions src/Encoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@

namespace Thesis\Protobuf;

use Thesis\Protobuf\Encoder\EncodingError;

/**
* @api
*/
interface Encoder
{
/**
* @throws EncodingError
* @throws ProtobufException
*/
public function encode(object $message): string;
}
3 changes: 3 additions & 0 deletions src/Encoder/Internal/ReflectionEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Thesis\Protobuf\Encoder\Internal;

use Thesis\Protobuf\Encoder;
use Thesis\Protobuf\ProtobufException;
use Thesis\Protobuf\Reflection\Reflector;
use Thesis\Protobuf\Serializer;

Expand All @@ -25,6 +26,8 @@ public function encode(object $message): string
return $this->serializer->serialize(
$this->reflector->message($message),
);
} catch (ProtobufException $e) {
throw $e;
} catch (\Throwable $e) {
throw new Encoder\EncodingError($e->getMessage(), (int) $e->getCode(), $e);
}
Expand Down
29 changes: 29 additions & 0 deletions src/Reflection/Exception/MappingError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Thesis\Protobuf\Reflection\Exception;

use Thesis\Protobuf\Reflection\ReflectionException;

/**
* @api
*/
final class MappingError extends ReflectionException
{
/**
* @param non-empty-list<ReflectionException> $reasons
*/
public function __construct(
public readonly array $reasons,
) {
parent::__construct(\sprintf(
'Multiple exceptions encountered (%d): %s',
\count($reasons),
implode('', array_map(
static fn(ReflectionException $reason) => \sprintf("\n\n%s: %s", $reason::class, $reason->getMessage()),
$this->reasons,
)),
));
}
}
24 changes: 24 additions & 0 deletions src/Reflection/Exception/PropertyRequired.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Thesis\Protobuf\Reflection\Exception;

use Thesis\Protobuf\Reflection\ReflectionException;

/**
* @api
*/
final class PropertyRequired extends ReflectionException
{
/**
* @param non-empty-string $class
* @param non-empty-string $property
*/
public function __construct(
public readonly string $class,
public readonly string $property,
) {
parent::__construct(\sprintf('The property "%s::$%s" is required and cannot be initialized implicitly.', $class, $property));
}
}
26 changes: 0 additions & 26 deletions src/Reflection/Exception/PropertyUninitialized.php

This file was deleted.

99 changes: 0 additions & 99 deletions src/Reflection/Internal/Visitor/ToDefaultValueTypeVisitor.php

This file was deleted.

4 changes: 3 additions & 1 deletion src/Reflection/ReflectionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace Thesis\Protobuf\Reflection;

use Thesis\Protobuf\ProtobufException;

/**
* @api
*/
abstract class ReflectionException extends \Exception {}
abstract class ReflectionException extends ProtobufException {}
Loading