Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Links are to strict #57

Merged
merged 11 commits into from
Apr 23, 2019
Merged
4 changes: 2 additions & 2 deletions integration-tests/HydraClient.EntryPoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe("Having a Hydra client", () => {

it("should obtain matching events", () => {
const matchingEvents = this.searchResult.hypermedia.collections
.where(item => item.iri.match("/api/events$"))
.where(item => item.iri.match("/api/events\?"))
.first();
expect(matchingEvents).toBeDefined();
expect(matchingEvents).not.toBeNull();
Expand All @@ -136,7 +136,7 @@ describe("Having a Hydra client", () => {

it("should obtain matching events", () => {
const matchingEvents = this.filteringResult.hypermedia
.where(item => item.iri.match("/api/events$") && item.type.contains(hydra.Collection))
.where(item => item.iri.match("/api/events\?") && item.type.contains(hydra.Collection))
.first();
expect(matchingEvents).toBeDefined();
expect(matchingEvents).not.toBeNull();
Expand Down
14 changes: 11 additions & 3 deletions integration-tests/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@ function setHeaders(path: string, response: express.Response, isJsonLd: boolean)
return true;
}

function loadBody(path: string) {
function loadBody(path: string, query: string) {
const file = __dirname + path + (path.indexOf(".") === -1 ? ".jsonld" : "");
if (fs.existsSync(file)) {
return fs.readFileSync(file, "utf8");
let result = fs.readFileSync(file, "utf8");
if (!!query) {
result = JSON.parse(result);
const matchingResource = !!result["@graph"] ? result["@graph"].find(_ => _["@id"] === path) : result;
matchingResource["@id"] = path + query;
result = JSON.stringify(result);
}

return result;
}

return null;
Expand All @@ -59,7 +67,7 @@ module.exports = {
});
server.get("/*", (request, response) => {
const path = request.path === "/" ? "/root" : request.path;
const body = loadBody(path);
const body = loadBody(path, request.originalUrl.substr(path.length));
if (setHeaders(path, response, !!body) || body) {
response.status(200).send(body);
} else {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
],
"files": [
"src/**/*.js",
"src/**/*.json",
"index.js",
"types"
],
Expand Down
10 changes: 9 additions & 1 deletion src/HydraClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IWebResource } from "./DataModel/IWebResource";
import { IHydraClient } from "./IHydraClient";
import { IHypermediaProcessor } from "./IHypermediaProcessor";
import { IIriTemplateExpansionStrategy } from "./IIiriTemplateExpansionStrategy";
import { LinksPolicy } from "./LinksPolicy";
import { hydra } from "./namespaces";

/**
Expand All @@ -30,18 +31,21 @@ export default class HydraClient implements IHydraClient {

private readonly hypermediaProcessors: IHypermediaProcessor[];
private readonly iriTemplateExpansionStrategy: IIriTemplateExpansionStrategy;
private readonly linksPolicy: LinksPolicy;
private readonly httpCall: (url: string, options?: RequestInit) => Promise<Response>;

/**
* Initializes a new instance of the {@link HydraClient} class.
* @param {Iterable<IHypermediaProcessor>} hypermediaProcessors Hypermedia processors used for response hypermedia
* controls extraction.
* @param {IIriTemplateExpansionStrategy} iriTemplateExpansionStrategy IRI template variable expansion strategy.
* @param {LinksPolicy} linksPolicy Policy defining what is a considered a link.
* @param {(url: string, options?: Request) => Promise<Response>} httpCall HTTP facility used to call remote server.
*/
public constructor(
hypermediaProcessors: Iterable<IHypermediaProcessor>,
iriTemplateExpansionStrategy: IIriTemplateExpansionStrategy,
linksPolicy: LinksPolicy = LinksPolicy.Strict,
httpCall: (url: string, options?: RequestInit) => Promise<Response>
) {
if (!FilterableCollection.prototype.any.call(hypermediaProcessors)) {
Expand All @@ -58,6 +62,7 @@ export default class HydraClient implements IHydraClient {

this.hypermediaProcessors = Array.from(hypermediaProcessors);
this.iriTemplateExpansionStrategy = iriTemplateExpansionStrategy;
this.linksPolicy = linksPolicy;
this.httpCall = httpCall;
}

Expand Down Expand Up @@ -116,7 +121,10 @@ export default class HydraClient implements IHydraClient {
throw new Error(HydraClient.responseFormatNotSupported);
}

const result = await hypermediaProcessor.process(response, this);
const result = await hypermediaProcessor.process(response, this, {
linksPolicy: this.linksPolicy,
originalUrl: url
});
Object.defineProperty(result, "iri", {
value: response.url
});
Expand Down
67 changes: 62 additions & 5 deletions src/HydraClientFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IIriTemplateExpansionStrategy } from "./IIiriTemplateExpansionStrategy"
import IndirectTypingProvider from "./JsonLd/IndirectTypingProvider";
import JsonLdHypermediaProcessor from "./JsonLd/JsonLdHypermediaProcessor";
import StaticOntologyProvider from "./JsonLd/StaticOntologyProvider";
import { LinksPolicy } from "./LinksPolicy";
/* tslint:disable:no-var-requires */
const hydraOntology = require("./JsonLd/hydra.json");

Expand All @@ -15,6 +16,7 @@ const hydraOntology = require("./JsonLd/hydra.json");
export default class HydraClientFactory {
private readonly hypermediaProcessors: IHypermediaProcessor[] = [];
private iriTemplateExpansionStrategy: IIriTemplateExpansionStrategy = null;
private linksPolicy: LinksPolicy = LinksPolicy.Strict;
private httpCall: (url: string, options?: RequestInit) => Promise<Response> = null;

/**
Expand All @@ -25,26 +27,75 @@ export default class HydraClientFactory {
return new HydraClientFactory();
}

private static createJsonLdHypermediaProcessor() {
return new JsonLdHypermediaProcessor(new IndirectTypingProvider(new StaticOntologyProvider(hydraOntology)));
}

/**
* Configures a future {@link IHydraClient} with {@link JsonLdHypermediaProcessor},
* {@link BodyResourceBoundIriTemplateExpansionStrategy} and fetch components.
* @returns {HydraClientFactory}
*/
public withDefaults(): HydraClientFactory {
const jsonLdHypermediaProcessor = new JsonLdHypermediaProcessor(
new IndirectTypingProvider(new StaticOntologyProvider(hydraOntology))
);
return this.with(jsonLdHypermediaProcessor)
return this.withJsonLd()
.with(new BodyResourceBoundIriTemplateExpansionStrategy())
.withStrictLinks()
.with(fetch.bind(window));
}

/**
* Configures a factory to create a client with explicitly defined links.
* @returns {HydraClientFactory}
*/
public withStrictLinks(): HydraClientFactory {
this.linksPolicy = LinksPolicy.Strict;
return this;
}

/**
* Configures a factory to create a client with links of resources from the same host and port.
* @returns {HydraClientFactory}
*/
public withSameRootLinks(): HydraClientFactory {
this.linksPolicy = LinksPolicy.SameRoot;
return this;
}

/**
* Configures a factory to create a client with all resources from HTTP/HTTPS considered links.
* @returns {HydraClientFactory}
*/
public withAllHttpLinks(): HydraClientFactory {
this.linksPolicy = LinksPolicy.AllHttp;
return this;
}

/**
* Configures a factory to create a client with all resources considered links.
* @returns {HydraClientFactory}
*/
public withAllLinks(): HydraClientFactory {
this.linksPolicy = LinksPolicy.All;
return this;
}

/**
* Configures a factory with JSON-LD hypermedia processor.
* @returns {HydraClientFactory}
*/
public withJsonLd(): HydraClientFactory {
this.with(HydraClientFactory.createJsonLdHypermediaProcessor());
return this;
}

/**
* Adds an another {@link IHypermediaProcessor} component.
* @param {IHypermediaProcessor} hypermediaProcessor Hypermedia processor to be passed
* to future {@link HydraClient} instances.
* @returns {HydraClientFactory}
*/
public with(hypermediaProcessor: IHypermediaProcessor): HydraClientFactory;

/**
* Sets a {@link IIriTemplateExpansionStrategy} component.
* @param {IIriTemplateExpansionStrategy} iriTemplateExpansionStrategy IRI template expansion strategy to be used
Expand All @@ -53,6 +104,7 @@ export default class HydraClientFactory {
*/
/* tslint:disable-next-line:unified-signatures */
public with(iriTemplateExpansionStrategy: IIriTemplateExpansionStrategy): HydraClientFactory;

/**
* Adds HTTP requests facility component.
* @param {(url: string, options?: RequestInit) => Promise<Response>} httpCall HTTP call facility to be used for
Expand All @@ -78,6 +130,11 @@ export default class HydraClientFactory {
* @returns {IHydraClient}
*/
public andCreate(): IHydraClient {
return new HydraClient(this.hypermediaProcessors, this.iriTemplateExpansionStrategy, this.httpCall);
return new HydraClient(
this.hypermediaProcessors,
this.iriTemplateExpansionStrategy,
this.linksPolicy,
this.httpCall
);
}
}
16 changes: 16 additions & 0 deletions src/IHypermediaProcessingOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { LinksPolicy } from "./LinksPolicy";

/**
* Describes a {@link IHypermediaProcessor} processing options.
*/
export interface IHypermediaProcessingOptions {
/**
* Gets a policy defining which related resources will be added to links collection.
*/
readonly linksPolicy: LinksPolicy;

/**
* Gets an originally requested Url. This may be different than the one provided in the Response.url after redirects.
*/
readonly originalUrl: string;
}
25 changes: 6 additions & 19 deletions src/IHypermediaProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import { IWebResource } from "./DataModel/IWebResource";
import HydraClient from "./HydraClient";

export enum Level {
/**
* @enum
* @type {number}
* Defines a not supported response.
*/
None = 0,

/**
* @enum
* @type {number}
* Defines an exact support of the response.
*/
FullSupport = 100
}
import { IHydraClient } from "./IHydraClient";
import { IHypermediaProcessingOptions } from "./IHypermediaProcessingOptions";
import { Level } from "./Level";

/**
* Describes an abstract meta-data providing facility which translates from a raw {@link Response}
Expand All @@ -40,8 +26,9 @@ export interface IHypermediaProcessor {
/**
* Parses a given raw response.
* @param {Response} response Raw fetch response holding data to be parsed.
* @param client {HydraClient} Hydra client.
* @param client {IHydraClient} Hydra client.
* @param options {IHypermediaProcessingOptions} Additional processing options.
* @returns {Promise<IWebResource>}
*/
process(response: Response, client: HydraClient): Promise<IWebResource>;
process(response: Response, client: IHydraClient, options: IHypermediaProcessingOptions): Promise<IWebResource>;
}
4 changes: 2 additions & 2 deletions src/JsonLd/IndirectTypingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default class IndirectTypingProvider {
}

private async isInRangeOfPredicate(expectedType: string, processingState: ProcessingState): Promise<boolean> {
const ownerResource = processingState.payload.find(resource => resource["@id"] === processingState.parentIri);
const ownerResource = processingState.findRawResource(processingState.parentIri);
if (!ownerResource) {
return false;
}
Expand All @@ -57,7 +57,7 @@ export default class IndirectTypingProvider {
const range = await this.ontologyProvider.getRangeFor(property);
if (
range === expectedType &&
ownerResource[property].find(resource => resource["@id"] === processingState.processedObject["@id"])
ownerResource[property].find(_ => _["@id"] === processingState.processedObject["@id"])
) {
return true;
}
Expand Down
Loading