Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
27pchrisl committed Oct 31, 2020
2 parents f4ed0cc + 5fe6a21 commit c80118a
Show file tree
Hide file tree
Showing 19 changed files with 115 additions and 25 deletions.
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# Flat3 OData 4.01 Producer for Laravel

<p align="center">
<a href="https://github.com/flat3/lodata/actions"><img src="https://github.com/flat3/lodata/workflows/Tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/flat3/lodata"><img src="https://img.shields.io/packagist/v/flat3/lodata" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/flat3/lodata"><img src="https://img.shields.io/packagist/l/flat3/lodata" alt="License"></a>
</p>

## What is OData? (from the OData spec)

The OData Protocol is an application-level protocol for interacting with data via RESTful interfaces. The protocol supports the description of data models and the editing and querying of data according to those models. It provides facilities for:
The OData Protocol is an application-level protocol for interacting with data via RESTful interfaces. The protocol supports the
description of data models and the editing and querying of data according to those models. It provides facilities for:

- Metadata: a machine-readable description of the data model exposed by a particular service.
- Data: sets of data entities and the relationships between them.
Expand All @@ -11,14 +18,35 @@ The OData Protocol is an application-level protocol for interacting with data vi
- Operations: invoking custom logic
- Vocabularies: attaching custom semantics

The OData Protocol is different from other REST-based web service approaches in that it provides a uniform way to describe both the data and the data model. This improves semantic interoperability between systems and allows an ecosystem to emerge.
The OData Protocol is different from other REST-based web service approaches in that it provides a uniform way to describe
both the data and the data model. This improves semantic interoperability between systems and allows an ecosystem to emerge.
Towards that end, the OData Protocol follows these design principles:
- Prefer mechanisms that work on a variety of data sources. In particular, do not assume a relational data model.
- Extensibility is important. Services should be able to support extended functionality without breaking clients unaware of those extensions.
- Follow REST principles.
- OData should build incrementally. A very basic, compliant service should be easy to build, with additional work necessary only to support additional capabilities.
- Keep it simple. Address the common cases and provide extensibility where necessary.

## Getting started

First require lodata inside your existing Laravel application:

```
composer require flat3/lodata
```

Now start your app, the OData API endpoint will now be available at: http://127.0.0.1:8000/odata (or whichever port your application normally runs on).

If you access that URL you'll see an "unauthorized" message. By default the endpoint is wrapped in HTTP Basic Authentication.
You can either provide basic auth credentials of an existing user, or you can temporarily disable authentication by including this in your
Laravel .env file:

```
LODATA_DISABLE_AUTH=1
```

Access the URL again, you'll see the Service Document. The Metadata Document will also be available at: http://127.0.0.1:8000/odata/$metadata

## Specification

* https://docs.oasis-open.org/odata/odata/v4.01/os/part1-protocol/odata-v4.01-os-part1-protocol.html
Expand Down
9 changes: 9 additions & 0 deletions src/Controller/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,15 @@ public function getId(): string
return $this->id;
}

public function replaceQueryParams(Transaction $incomingTransaction): self
{
foreach (['select'] as $param) {
$this->$param = $incomingTransaction->$param;
}

return $this;
}

public function execute(): EmitInterface
{
$pathSegments = $this->getPathSegments();
Expand Down
10 changes: 7 additions & 3 deletions src/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,12 +322,16 @@ public function getResourceUrl(Transaction $transaction): string
);
}

public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
$transaction = $this->transaction ?: $transaction;
if ($this->transaction) {
$transaction = $this->transaction->replaceQueryParams($transaction);
}

$context = $context ?: $this;

$this->metadata = [
'context' => $this->getContextUrl($transaction),
'context' => $context->getContextUrl($transaction),
];

return $transaction->getResponse()->setCallback(function () use ($transaction) {
Expand Down
10 changes: 7 additions & 3 deletions src/EntitySet.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,18 @@ public function emit(Transaction $transaction): void
$transaction->outputJsonArrayEnd();
}

public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
$transaction = $this->transaction ?: $transaction;
if ($this->transaction) {
$transaction = $this->transaction->replaceQueryParams($transaction);
}

$context = $context ?: $this;

$setCount = $this->count();

$metadata = [
'context' => $this->getContextUrl($transaction),
'context' => $context->getContextUrl($transaction),
];

$count = $transaction->getCount();
Expand Down
9 changes: 5 additions & 4 deletions src/Helper/PropertyValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,20 +190,21 @@ public function emit(Transaction $transaction): void
);
}

public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
$value = $this->value;
$context = $context ?: $this;

if ($value instanceof Primitive && null === $value->get() || $value === null) {
throw new NoContentException('null_value');
}

if ($value instanceof Entity) {
return $value->response($transaction);
if ($value instanceof Entity || $value instanceof EntitySet) {
return $value->response($transaction, $this);
}

$metadata = [
'context' => $this->getContextUrl($transaction),
'context' => $context->getContextUrl($transaction),
];

$metadata = $transaction->getMetadata()->filter($metadata);
Expand Down
2 changes: 1 addition & 1 deletion src/Interfaces/EmitInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ interface EmitInterface
{
public function emit(Transaction $transaction): void;

public function response(Transaction $transaction): Response;
public function response(Transaction $transaction, ?ContextInterface $context = null): Response;
}
3 changes: 2 additions & 1 deletion src/PathSegment/Count.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Flat3\Lodata\Controller\Transaction;
use Flat3\Lodata\Exception\Internal\PathNotHandledException;
use Flat3\Lodata\Exception\Protocol\BadRequestException;
use Flat3\Lodata\Interfaces\ContextInterface;
use Flat3\Lodata\Interfaces\EmitInterface;
use Flat3\Lodata\Interfaces\PipeInterface;

Expand All @@ -25,7 +26,7 @@ public function emit(Transaction $transaction): void
$transaction->outputRaw($this->countable->count());
}

public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
return $transaction->getResponse()->setCallback(function () use ($transaction) {
$this->emit($transaction);
Expand Down
3 changes: 2 additions & 1 deletion src/PathSegment/Metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Flat3\Lodata\Exception\Protocol\BadRequestException;
use Flat3\Lodata\Facades\Lodata;
use Flat3\Lodata\Helper\Constants;
use Flat3\Lodata\Interfaces\ContextInterface;
use Flat3\Lodata\Interfaces\EmitInterface;
use Flat3\Lodata\Interfaces\PipeInterface;
use Flat3\Lodata\NavigationBinding;
Expand Down Expand Up @@ -240,7 +241,7 @@ public function emit(Transaction $transaction): void
$transaction->outputRaw($root->asXML());
}

public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
$transaction->ensureMethod(Request::METHOD_GET);

Expand Down
3 changes: 2 additions & 1 deletion src/PathSegment/Service.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
use Flat3\Lodata\Controller\Response;
use Flat3\Lodata\Controller\Transaction;
use Flat3\Lodata\Facades\Lodata;
use Flat3\Lodata\Interfaces\ContextInterface;
use Flat3\Lodata\Interfaces\EmitInterface;
use Flat3\Lodata\Interfaces\ServiceInterface;
use Illuminate\Http\Request;

class Service implements EmitInterface
{
public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
$transaction->ensureMethod(Request::METHOD_GET);

Expand Down
3 changes: 2 additions & 1 deletion src/PathSegment/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Flat3\Lodata\Exception\Protocol\BadRequestException;
use Flat3\Lodata\Exception\Protocol\NoContentException;
use Flat3\Lodata\Helper\PropertyValue;
use Flat3\Lodata\Interfaces\ContextInterface;
use Flat3\Lodata\Interfaces\EmitInterface;
use Flat3\Lodata\Interfaces\PipeInterface;
use Flat3\Lodata\Primitive;
Expand Down Expand Up @@ -46,7 +47,7 @@ public static function pipe(
return new static($value);
}

public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
if (null === $this->primitive->get()) {
throw new NoContentException('null_value');
Expand Down
6 changes: 4 additions & 2 deletions src/Primitive.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,16 @@ public function emit(Transaction $transaction): void
$transaction->outputJsonValue($this);
}

public function response(Transaction $transaction): Response
public function response(Transaction $transaction, ?ContextInterface $context = null): Response
{
if (null === $this->get()) {
throw new NoContentException('null_value');
}

$context = $context ?: $this;

$metadata = [
'context' => $this->getContextUrl($transaction),
'context' => $context->getContextUrl($transaction),
];

$metadata = $transaction->getMetadata()->filter($metadata);
Expand Down
23 changes: 22 additions & 1 deletion tests/Unit/Eloquent/EloquentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use Flat3\Lodata\Tests\Request;
use Flat3\Lodata\Tests\TestCase;
use Flat3\Lodata\Transaction\Metadata;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;

class EloquentTest extends TestCase
{
Expand Down Expand Up @@ -356,6 +355,28 @@ public function test_expand_belongsto_property()
);
}

public function test_expand_belongsto_property_select_nonexistent_property()
{
$this->withFlightData();

$this->assertBadRequest(
Request::factory()
->path('/Flights(1)/passengers')
->query('$select', 'naame')
);
}

public function test_expand_belongsto_property_select_existent_property()
{
$this->withFlightData();

$this->assertJsonResponse(
Request::factory()
->path('/Flights(1)/passengers')
->query('$select', 'name')
);
}

public function test_expand_belongsto_property_full_metadata()
{
$this->withFlightData();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@context": "http://localhost/odata/$metadata#Flights/$entity",
"@context": "http://localhost/odata/$metadata#Passengers(1)/flight",
"id": 1,
"origin": "lhr",
"destination": "lax",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@context": "http://localhost/odata/$metadata#Flights/$entity",
"@context": "http://localhost/odata/$metadata#Passengers(1)/flight",
"@type": "#com.example.odata.Flight",
"@id": "http://localhost/odata/Flights(1)",
"@readLink": "http://localhost/odata/Flights(1)",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"@context": "http://localhost/odata/$metadata#Flights(1)/passengers",
"value": [
{
"name": "Anne Arbor"
},
{
"name": "Bob Barry"
},
{
"name": "Charlie Carrot"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
httpCode: 400
odataCode: property_does_not_exist
message: 'The requested property "naame" does not exist on this entity type'
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@context": "http://localhost/odata/$metadata#Countries/$entity",
"@context": "http://localhost/odata/$metadata#Airports(1)/country",
"id": 1,
"name": "en"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@context": "http://localhost/odata/$metadata#Airports/$entity",
"@context": "http://localhost/odata/$metadata#Flights(1)/originAirport",
"id": 1,
"name": "Heathrow",
"code": "lhr",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@context": "http://localhost/odata/$metadata#Airports/$entity",
"@context": "http://localhost/odata/$metadata#Flights(3)/destinationAirport",
"id": 2,
"name": "Los Angeles",
"code": "lax",
Expand Down

0 comments on commit c80118a

Please sign in to comment.