Skip to content

Commit

Permalink
Fix handling for streamed LookupResources and LookupSubjects response…
Browse files Browse the repository at this point in the history
…s that are in json-lines format
  • Loading branch information
thewunder committed Oct 12, 2023
1 parent e64e8a6 commit f190c16
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 43 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## v0.5.1

Fix handling for streamed LookupResources and LookupSubjects responses that are in [JSON lines](https://jsonlines.org/)
format rather than valid JSON.

## v0.5.0

Major new release up to date with SpiceDB 1.25+. Except for the experimental APIs, things should be stable going
Expand Down
4 changes: 3 additions & 1 deletion src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use Chiphpotle\Rest\Model\WriteSchemaRequest;
use Chiphpotle\Rest\Model\WriteSchemaResponse;
use Chiphpotle\Rest\Normalizer\JaneObjectNormalizer;
use Chiphpotle\Rest\Runtime\Client\JsonLinesDecoder;
use Http\Discovery\Psr17FactoryDiscovery;
use Symfony\Component\Serializer\Encoder\JsonDecode;
use Symfony\Component\Serializer\Encoder\JsonEncode;
Expand Down Expand Up @@ -176,7 +177,8 @@ public static function create($baseUrl, $apiKey, $additionalNormalizers = []): C
if (count($additionalNormalizers) > 0) {
$normalizers = array_merge($normalizers, $additionalNormalizers);
}
$serializer = new Serializer($normalizers, [new JsonEncoder(new JsonEncode(), new JsonDecode(['json_decode_associative' => true]))]);
$jsonDecoder = new JsonDecode(['json_decode_associative' => true]);
$serializer = new Serializer($normalizers, [new JsonEncoder(new JsonEncode(), $jsonDecoder), new JsonLinesDecoder($jsonDecoder)]);
return new self($httpClient, $requestFactory, $serializer, $streamFactory);
}
}
2 changes: 1 addition & 1 deletion src/Endpoint/PermissionsServiceLookupResources.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected function transformResponseBody(ResponseInterface $response, Serializer
$status = $response->getStatusCode();
$body = (string) $response->getBody();
if (200 === $status) {
return $serializer->deserialize($body, PermissionsResourcesPostResponse200::class, 'json');
return $serializer->deserialize($body, PermissionsResourcesPostResponse200::class, 'jsonl');
}
$this->throwRpcException($body, $serializer);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Endpoint/PermissionsServiceLookupSubjects.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected function transformResponseBody(ResponseInterface $response, Serializer
$status = $response->getStatusCode();
$body = (string) $response->getBody();
if (200 === $status) {
return $serializer->deserialize($body, PermissionsSubjectsPostResponse200::class, 'json');
return $serializer->deserialize($body, PermissionsSubjectsPostResponse200::class, 'jsonl');
}
$this->throwRpcException($body, $serializer);
}
Expand Down
17 changes: 13 additions & 4 deletions src/Model/PermissionsResourcesPostResponse200.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@

final class PermissionsResourcesPostResponse200
{
protected LookupResourcesResponse $result;
/**
* @var LookupResourcesResponse[]
*/
protected array $result;

protected ?RpcStatus $error;

public function getResult(): LookupResourcesResponse
/**
* @return LookupResourcesResponse[]
*/
public function getResults(): array
{
return $this->result;
}

public function setResult(LookupResourcesResponse $result): self
/**
* @param LookupResourcesResponse[] $results
*/
public function setResults(array $results): self
{
$this->result = $result;
$this->result = $results;
return $this;
}

Expand Down
16 changes: 13 additions & 3 deletions src/Model/PermissionsSubjectsPostResponse200.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@

final class PermissionsSubjectsPostResponse200
{
protected LookupSubjectsResponse $result;
/**
* @var LookupSubjectsResponse[]
*/
protected array $result;

protected RpcStatus $error;

public function getResult(): LookupSubjectsResponse
/**
* @return LookupSubjectsResponse[]
*/
public function getResults(): array
{
return $this->result;
}

public function setResult(LookupSubjectsResponse $result): self
/**
* @param LookupSubjectsResponse[] $result
* @return $this
*/
public function setResults(array $result): self
{
$this->result = $result;
return $this;
Expand Down
21 changes: 12 additions & 9 deletions src/Normalizer/PermissionsResourcesPostResponse200Normalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace Chiphpotle\Rest\Normalizer;

use Chiphpotle\Rest\Model\LookupResourcesResponse;
use Chiphpotle\Rest\Model\LookupSubjectsResponse;
use Chiphpotle\Rest\Model\PermissionsResourcesPostResponse200;
use Chiphpotle\Rest\Model\RpcStatus;
use Chiphpotle\Rest\Runtime\Client\RpcException;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
Expand Down Expand Up @@ -33,16 +35,17 @@ public function supportsNormalization($data, $format = null): bool
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): PermissionsResourcesPostResponse200
{
$object = new PermissionsResourcesPostResponse200();
if (null === $data || false === is_array($data)) {
return $object;
$results = [];
foreach ($data as $resultData) {
if (array_key_exists('result', $resultData)) {
$results[] = $this->denormalizer->denormalize($resultData['result'], LookupResourcesResponse::class, 'json', $context);
} elseif (array_key_exists('error', $resultData)) {
$rpcStatus = $this->denormalizer->denormalize($resultData['error'], RpcStatus::class, 'json', $context);
throw new RpcException($rpcStatus);
}
}
if (array_key_exists('result', $data)) {
$object->setResult($this->denormalizer->denormalize($data['result'], LookupResourcesResponse::class, 'json', $context));
}
if (array_key_exists('error', $data)) {
$object->setError($this->denormalizer->denormalize($data['error'], RpcStatus::class, 'json', $context));
}
return $object;

return $object->setResults($results);
}

public function normalize($object, $format = null, array $context = []): array
Expand Down
21 changes: 12 additions & 9 deletions src/Normalizer/PermissionsSubjectsPostResponse200Normalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Chiphpotle\Rest\Model\PermissionsSubjectsPostResponse200;
use ArrayObject;
use Chiphpotle\Rest\Model\RpcStatus;
use Chiphpotle\Rest\Runtime\Client\RpcException;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
Expand Down Expand Up @@ -34,16 +35,18 @@ public function supportsNormalization($data, $format = null): bool
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): PermissionsSubjectsPostResponse200
{
$object = new PermissionsSubjectsPostResponse200();
if (null === $data || false === is_array($data)) {
return $object;
}
if (array_key_exists('result', $data)) {
$object->setResult($this->denormalizer->denormalize($data['result'], LookupSubjectsResponse::class, 'json', $context));
}
if (array_key_exists('error', $data)) {
$object->setError($this->denormalizer->denormalize($data['error'], RpcStatus::class, 'json', $context));

$results = [];
foreach ($data as $resultData) {
if (array_key_exists('result', $resultData)) {
$results[] = $this->denormalizer->denormalize($resultData['result'], LookupSubjectsResponse::class, 'json', $context);
} elseif (array_key_exists('error', $resultData)) {
$rpcStatus = $this->denormalizer->denormalize($resultData['error'], RpcStatus::class, 'json', $context);
throw new RpcException($rpcStatus);
}
}
return $object;

return $object->setResults($results);
}

public function normalize($object, $format = null, array $context = []): array
Expand Down
35 changes: 35 additions & 0 deletions src/Runtime/Client/JsonLinesDecoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Chiphpotle\Rest\Runtime\Client;

use Symfony\Component\Serializer\Encoder\DecoderInterface;

/**
* Decodes JSON lines formatted responses
*
* @see https://jsonlines.org/
*/
final class JsonLinesDecoder implements DecoderInterface
{
public const FORMAT = 'jsonl';

public function __construct(private DecoderInterface $jsonDecoder)
{
}

public function decode(string $data, string $format, array $context = []): array
{
$decodedData = [];
foreach (explode("\n", $data) as $line) {
if (!empty($line)) {
$decodedData[] = $this->jsonDecoder->decode($line, $format, $context);
}
}
return $decodedData;
}

public function supportsDecoding(string $format): bool
{
return self::FORMAT === $format;
}
}
44 changes: 29 additions & 15 deletions test/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
use Chiphpotle\Rest\Model\LookupResourcesRequest;
use Chiphpotle\Rest\Model\LookupSubjectsRequest;
use Chiphpotle\Rest\Model\ObjectReference;
use Chiphpotle\Rest\Model\PermissionsResourcesPostResponse200;
use Chiphpotle\Rest\Model\PermissionsSubjectsPostResponse200;
use Chiphpotle\Rest\Model\ReadRelationshipsRequest;
use Chiphpotle\Rest\Model\Relationship;
use Chiphpotle\Rest\Model\RelationshipFilter;
Expand Down Expand Up @@ -110,48 +112,60 @@ public function testDeleteRelationships()

public function testLookupResources()
{
$this->writeRelationship('document', 'topsecret1', 'viewer', 'user', 'alice');
$this->writeRelationship('document', 'topsecret1', 'viewer', 'user', 'lookup_resource_test');
$writeResults = $this->writeRelationship('document', 'topsecret2', 'viewer', 'user', 'lookup_resource_test');

$request = new LookupResourcesRequest();
$request
->setResourceObjectType("document")
->setPermission("view")
->setSubject(SubjectReference::create("user", "alice"))
->setConsistency(Consistency::minimizeLatency());
->setSubject(SubjectReference::create("user", "lookup_resource_test"))
->setConsistency(Consistency::atLeastAsFresh($writeResults->getWrittenAt()));

$response = $this->getApiClient()->lookupResources(
$request
);
$this->assertEquals(
"topsecret1",
$response->getResult()->getResourceObjectId()
);

$this->assertInstanceOf(PermissionsResourcesPostResponse200::class, $response);
$results = $response->getResults();
$this->assertCount(2, $results);

foreach ($results as $result) {
$this->assertTrue(in_array($result->getResourceObjectId(), ['topsecret1', 'topsecret2']));
}
}

public function testLookupSubjects()
{
$this->writeRelationship('document', 'topsecret7', 'viewer', 'user', 'alice');
$this->writeRelationship('document', 'lookup_subject_test', 'viewer', 'user', 'alice');
$this->writeRelationship('document', 'lookup_subject_test', 'viewer', 'user', 'mary');

$request = new LookupSubjectsRequest(
ObjectReference::create('document', 'topsecret7'),
ObjectReference::create('document', 'lookup_subject_test'),
'view',
'user'
);
$response = $this->getApiClient()->lookupSubjects(
$request
);
$this->assertEquals(
"alice",
$response->getResult()->getSubjectObjectId()
);

$this->assertInstanceOf(PermissionsSubjectsPostResponse200::class, $response);
$results = $response->getResults();
$this->assertCount(2, $results);

foreach ($results as $result) {
$this->assertTrue(in_array($result->getSubjectObjectId(), ['alice', 'mary']));
}
}

public function testPermissionCheckValid()
{
$this->writeRelationship('document', 'topsecret1', 'viewer', 'user', 'bob');
$writeResponse = $this->writeRelationship('document', 'topsecret1', 'viewer', 'user', 'bob');
$request = new CheckPermissionRequest(
ObjectReference::create("document", "topsecret1"),
"view",
SubjectReference::create("user", "bob")
SubjectReference::create("user", "bob"),
Consistency::atLeastAsFresh($writeResponse->getWrittenAt())
);
$response = $this->getApiClient()->checkPermission(
$request
Expand Down

0 comments on commit f190c16

Please sign in to comment.