Commit 211a8948 authored by Guillaume Perréal's avatar Guillaume Perréal

Génération d'un APIClient au lieu d'utiliser une AbstractResourceCache.

parent c8d285d6
......@@ -179,6 +179,19 @@ final class OperationDef implements \JsonSerializable, HasName
return $this->isCollection;
}
/**
* @return string
*/
public function __toString(): string
{
return sprintf('operation "%s" (method=%s, isCollection=%s, special=%s)',
$this->originalName,
$this->method,
$this->isCollection ? "yes" : "no",
$this->special
);
}
/**
* {@inheritdoc}
*/
......
......@@ -104,12 +104,9 @@ final class OperationMapper
}
$opParameters = $body = [];
$callParameters = [TypescriptHelper::quoteString($this->operation->getMethod())];
$callPath = $path = $this->pathParser->parse($this->operation->getPath(), $this->operation->getRequirements());
$iriParameterName = null;
if ($this->iri) {
$iriParameter = new Parameter('iri', Placeholder::get(sprintf('IRI<%s>', $this->operation->getClassName())));
......@@ -118,14 +115,12 @@ final class OperationMapper
if ($callPath === $this->iri) {
$opParameters[] = $iriParameter;
$iriParameterName = $iriParameter->getName();
$callParameters[] = $iriParameterName . ' as any';
$urlParameter = $iriParameter->getName();
} else {
foreach ($callPath->getParameters() as $parameter) {
$opParameters[] = $parameter;
}
$body[] = 'const path = ' . $callPath->getUsage() . ';';
$callParameters[] = 'path';
$urlParameter = $callPath->getUsage();
}
$denormalization = $this->operation->getDenormalization();
......@@ -137,7 +132,6 @@ final class OperationMapper
}
$opParameters[] = new Parameter('body', $requestBody);
$body[] = 'options.body = body;';
}
$filterProperties = $this->getFilterProperties();
......@@ -148,11 +142,10 @@ final class OperationMapper
}
$opParameters[] = new Parameter('options', Placeholder::get('RequestOptions'), false, '{}');
$callParameters[] = 'options';
$returnType = $responseBody ?: Placeholder::get('HttpResponseBase');
$returnType = $responseBody ?: BuiltinType::get('void');
$body[] = sprintf('return %s;', $this->buildClientCall($callParameters, $returnType, $iriParameterName));
$body[] = sprintf('return %s;', $this->buildClientCall(TypescriptHelper::quoteString($this->operation->getMethod()), $urlParameter, 'body', 'options', $returnType));
return new Operation(
$this->operation->getName(),
......@@ -186,41 +179,41 @@ final class OperationMapper
}
/**
* @param array $callParameters
* @param Type $returnType
* @param string|null $iriParameterName
* @param string $method
* @param string $path
* @param string $body
* @param string $options
* @param Type $returnType
*
* @return string
*/
private function buildClientCall(array $callParameters, Type $returnType, ?string $iriParameterName): string
private function buildClientCall(string $method, string $path, string $body, string $options, Type $returnType): string
{
$clientCall = sprintf(
'this.client.request<%s>(%s)',
$returnType->getUsage(),
implode(', ', $callParameters)
);
$opDef = $this->operation->getOpDef();
if ($opDef->isDeleteItem()) {
return "this.client.deleteItem($path, $options)";
}
$type = $returnType->getUsage();
if ($opDef->isGetCollection()) {
return "this.cache.getAll($clientCall)";
return "this.client.getCollection<$type>($path, $options)";
}
if ($opDef->isCreateItem()) {
return "this.cache.post($clientCall)";
return "this.client.postItem<$type>($path, $body, $options)";
}
if ($iriParameterName) {
if ($opDef->isGetItem()) {
return "this.cache.get($iriParameterName, () => $clientCall)";
}
if ($opDef->isUpdateItem()) {
return "this.cache.put($iriParameterName, $clientCall)";
}
if ($opDef->isDeleteItem()) {
return "this.cache.delete($iriParameterName, $clientCall)";
}
if ($opDef->isUpdateItem()) {
return "this.client.putItem<$type>($path, $body, $options)";
}
if ($opDef->isGetItem()) {
return "this.client.getItem<$type>($path, $options)";
}
return $clientCall;
return "this.client.request<$type>($method, $path, $options)";
}
/**
......
{% extends '@NgModelGenerator/_layout.ts.twig' %}
{% block content %}
import {HttpClient, HttpHeaders, HttpResponseBase} from '@angular/common/http';
import { forkJoin, Observable } from 'rxjs';
/**
......@@ -46,7 +45,7 @@ export interface Resource {
readonly [IRI_PROPERTY]: IRI<any>;
readonly [TYPE_PROPERTY]: string;
[property: string]: any;
};
}
/**
* Collection représente une collection de respoucres JSON-LD pour un type T donné.
......@@ -92,62 +91,48 @@ export type DateTime = string;
* Options supplémentaires pour les requêtes.
*/
export interface RequestOptions {
body?: any;
headers?: HttpHeaders | {
[header: string]: string | string[];
};
headers?: { [header: string]: string | string[]; };
params?: { [param: string]: string | string[]; };
}
/**
* AbstractResourceCache est une classe abstraite qui définit l'interface d'un cache de ressources.
*
* Elle doit être implémentée puis être fournie en provider d'un module core. Par exemple:
*
* final class ResourceCache<T extends Resource> extends AbstractResourceCache<T> {
* // Implémentation
* }
*
* providers: [
* [ provider: AbstractResourceCache, useClass: ResourceCache ],
* ]
* Client d'API, à implémenter.
*/
export abstract class AbstractResourceCache {
/**
* Récupère une ressource par son IRI. N'exécute la requête requestFactory que si on ne dispose
* pas d'une version en cache.
export abstract class APIClient {
/**
* Récupère une ressource par son IRI.
*/
public abstract get<R extends Resource>(
iri: IRI<R>,
requestFactory: () => Observable<R>
): Observable<R>;
public abstract getItem<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<R>;
/**
* Met à jour une ressource existante, rafraîchit le cache local avec la réponse.
* Met à jour une ressource existante.
*/
public abstract put<R extends Resource>(iri: IRI<R>, request: Observable<R>): Observable<R>;
public abstract putItem<R extends Resource>(iri: IRI<R>, item: any, options?: RequestOptions): Observable<R>;
/**
* Crée une nouvelle ressource et met la ressource créée dans le cache.
*/
public abstract post<R extends Resource>(request: Observable<R>): Observable<R>;
public abstract postItem<R extends Resource>(iri: string, item: any, options?: RequestOptions): Observable<R>;
/**
* Supprime une ressource en distant et dans le cache.
*/
public abstract delete<R extends Resource>(iri: IRI<R>, request: Observable<HttpResponseBase>): Observable<HttpResponseBase>;
public abstract deleteItem<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<void>;
/**
* Effectue une recherche et met en cache toutes les ressources récupérées.
*/
public abstract getAll<R extends Resource>(
request: Observable<Collection<R>>
): Observable<Collection<R>>;
public abstract getCollection<C extends Collection<Resource>>(iri: string, options?: RequestOptions): Observable<C>;
/**
* Invalide une ressource en cache.
*/
public abstract invalidate<R extends Resource>(iri: IRI<R>): void;
public abstract invalidateItem<R extends Resource>(iri: IRI<R>): void;
/**
* Effectue une requête arbitraire.
*/
public abstract request<T>(method: string, url: string, options?: RequestOptions): Observable<T>;
}
/**
......@@ -249,8 +234,7 @@ export class ResourceMetadata<R extends Resource, T extends string, P extends IR
export abstract class AbstractRepository<R extends Resource, T extends string, P extends IRIParameters> {
public constructor(
public readonly metadata: ResourceMetadata<R, T, P>,
protected readonly client: HttpClient,
protected readonly cache: AbstractResourceCache
protected readonly client: APIClient
) {}
/**
......@@ -379,8 +363,7 @@ export abstract class AbstractAPIService<
public constructor(
public readonly metadata: MR,
public readonly repositories: RR,
private readonly cache: AbstractResourceCache,
private readonly client: HttpClient
private readonly client: APIClient
) {}
public get<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<R>;
......@@ -398,7 +381,7 @@ export abstract class AbstractAPIService<
iri = typeOrIRI as IRI<R>;
options = parametersOrOptions as RequestOptions;
}
return this.cache.get(iri, () => this.client.get<R>(iri as any, options));
return this.client.getItem<R>(iri, options);
}
public getMany<R extends Resource>(iris: IRI<R>[], options?: RequestOptions): Observable<R[]> {
......@@ -410,7 +393,7 @@ export abstract class AbstractAPIService<
}
public invalidate<R extends Resource>(iri: IRI<R>): void {
this.cache.invalidate(iri);
this.client.invalidateItem(iri);
}
}
......
......@@ -5,11 +5,10 @@
{% block content %}
{% autoescape false %}
import { Injectable, Provider } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {
AbstractAPIService,
AbstractResourceCache,
APIClient,
APIMeta,
APIRepositoryRegistry,
IRIMetadata,
......@@ -93,13 +92,12 @@ export class AppMetadata extends LazyMetadataRegistry<AppAPI> {
@Injectable()
export class AppRepositories implements APIRepositoryRegistry<AppAPI> {
{% for repo in repositories %}
public readonly {{ repo.resourceName }} = new {{ repo.name }}(this.metadata.{{ repo.resourceName }}, this.client, this.cache);
public readonly {{ repo.resourceName }} = new {{ repo.name }}(this.metadata.{{ repo.resourceName }}, this.client);
{% endfor %}
public constructor(
public readonly metadata: AppMetadata,
private readonly cache: AbstractResourceCache,
private readonly client: HttpClient
private readonly client: APIClient
) {}
public get<T extends keyof AppAPI>(type: T): AppAPI[T]['repository'] {
......@@ -112,14 +110,6 @@ export class AppRepositories implements APIRepositoryRegistry<AppAPI> {
*/
@Injectable()
export class AppAPIService extends AbstractAPIService<AppAPI, AppMetadata, AppRepositories> {
public constructor(
metadata: AppMetadata,
repositories: AppRepositories,
cache: AbstractResourceCache,
client: HttpClient
) {
super(metadata, repositories, cache, client);
}
}
/* Provider factories */
......
......@@ -5,7 +5,6 @@
{% block content %}
{% autoescape false %}
import { Observable } from 'rxjs';
import { HttpResponseBase } from '@angular/common/http';
// @ts-ignore
import { AbstractRepository, Collection, IRI, RequestOptions, UUID } from './common';
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment