From e0450a9a1bdc09c4d8dc217b99d73120e8adeddc Mon Sep 17 00:00:00 2001 From: alien-mcl Date: Sat, 13 Jan 2018 19:56:38 +0100 Subject: [PATCH 1/6] Introduced a collections helper property on IHypermediaContainer as mentioned in HydraCG/Specifications#155. --- integration-tests/HydraClient.spec.ts | 8 +++----- src/DataModel/HypermediaContainer.ts | 10 +++++++++- src/DataModel/IHypermediaContainer.ts | 6 ++++++ src/JsonLd/JsonLdHypermediaProcessor.ts | 1 + tests/JsonLd/JsonLdMetadataProvider.spec.ts | 5 +++++ 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/integration-tests/HydraClient.spec.ts b/integration-tests/HydraClient.spec.ts index 42fb18f2..929e7018 100644 --- a/integration-tests/HydraClient.spec.ts +++ b/integration-tests/HydraClient.spec.ts @@ -35,9 +35,7 @@ describe("Having a Hydra client", () => { }); it("should obtain a collection of events", () => { - const collection = this.entryPoint.hypermedia - .where(item => item.iri.match("/api/events$") && item.type.contains(hydra.Collection)) - .first(); + const collection = this.entryPoint.hypermedia.collections.where(item => item.iri.match("/api/events$")).first(); expect(collection).toBeDefined(); expect(collection).not.toBeNull(); }); @@ -108,8 +106,8 @@ describe("Having a Hydra client", () => { ); it("should obtain matching events", () => { - const matchingEvents = this.searchResult.hypermedia - .where(item => item.iri.match("/api/events$") && item.type.contains(hydra.Collection)) + const matchingEvents = this.searchResult.hypermedia.collections + .where(item => item.iri.match("/api/events$")) .first(); expect(matchingEvents).toBeDefined(); expect(matchingEvents).not.toBeNull(); diff --git a/src/DataModel/HypermediaContainer.ts b/src/DataModel/HypermediaContainer.ts index 38b1bb75..2adc2d3f 100644 --- a/src/DataModel/HypermediaContainer.ts +++ b/src/DataModel/HypermediaContainer.ts @@ -1,6 +1,8 @@ +import { hydra } from "../namespaces"; import LinksCollection from "./Collections/LinksCollection"; import OperationsCollection from "./Collections/OperationsCollection"; import ResourceFilterableCollection from "./Collections/ResourceFilterableCollection"; +import { ICollection } from "./ICollection"; import { IHypermediaContainer } from "./IHypermediaContainer"; import { IResource } from "./IResource"; @@ -12,6 +14,8 @@ export default class HypermediaContainer extends ResourceFilterableCollection; + public readonly collections: ResourceFilterableCollection; + public readonly operations: OperationsCollection; public readonly links: LinksCollection; @@ -21,7 +25,8 @@ export default class HypermediaContainer extends ResourceFilterableCollection} Hypermedia controls to be stored within this container. * @param operations {OperationsCollection} Operations available on the container. * @param links {LinksCollection} Links available on the container. - * @param members {Iterable} Optional Hydra collection members in case container is a collection. + * @param members {ResourceFilterableCollection} Optional Hydra collection members in case + * container is a collection. */ public constructor( items: Iterable, @@ -31,6 +36,9 @@ export default class HypermediaContainer extends ResourceFilterableCollection(Array.from(items).filter(control => + control.type.contains(hydra.Collection) + ) as ICollection[]); this.links = links; this.members = members instanceof ResourceFilterableCollection ? members : new ResourceFilterableCollection(members); diff --git a/src/DataModel/IHypermediaContainer.ts b/src/DataModel/IHypermediaContainer.ts index 85af689c..5a386f99 100644 --- a/src/DataModel/IHypermediaContainer.ts +++ b/src/DataModel/IHypermediaContainer.ts @@ -1,5 +1,6 @@ import OperationsCollection from "./Collections/OperationsCollection"; import ResourceFilterableCollection from "./Collections/ResourceFilterableCollection"; +import { ICollection } from "./ICollection"; import { IResource } from "./IResource"; /** @@ -21,4 +22,9 @@ export interface IHypermediaContainer extends ResourceFilterableCollection; } diff --git a/src/JsonLd/JsonLdHypermediaProcessor.ts b/src/JsonLd/JsonLdHypermediaProcessor.ts index a41eb164..d3aea54d 100644 --- a/src/JsonLd/JsonLdHypermediaProcessor.ts +++ b/src/JsonLd/JsonLdHypermediaProcessor.ts @@ -2,6 +2,7 @@ import { promises as jsonLd } from "jsonld"; import ApiDocumentation from "../DataModel/ApiDocumentation"; import LinksCollection from "../DataModel/Collections/LinksCollection"; import OperationsCollection from "../DataModel/Collections/OperationsCollection"; +import ResourceFilterableCollection from "../DataModel/Collections/ResourceFilterableCollection"; import TypesCollection from "../DataModel/Collections/TypesCollection"; import HypermediaContainer from "../DataModel/HypermediaContainer"; import { IApiDocumentation } from "../DataModel/IApiDocumentation"; diff --git a/tests/JsonLd/JsonLdMetadataProvider.spec.ts b/tests/JsonLd/JsonLdMetadataProvider.spec.ts index a30609cd..de944257 100644 --- a/tests/JsonLd/JsonLdMetadataProvider.spec.ts +++ b/tests/JsonLd/JsonLdMetadataProvider.spec.ts @@ -36,6 +36,11 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { expect(this.result).toEqual(inputJsonLd); }); + it("should discover all collections", () => { + expect(this.result.hypermedia.collections.length).toBe(1); + expect(this.result.hypermedia.collections.first().iri).toMatch("/api/events$"); + }); + it("should separate hypermedia", () => { expect(this.result.hypermedia).toBeLike([ { From bbb64534def87105c2ad2272e508fa516e6ae28d Mon Sep 17 00:00:00 2001 From: alien-mcl Date: Sun, 21 Jan 2018 21:16:39 +0100 Subject: [PATCH 2/6] Added support for hydra:collection predicate --- integration-tests/server/api/context.jsonld | 4 + .../Collections/FilterableCollection.ts | 13 ++ src/DataModel/Collections/TypesCollection.ts | 2 +- src/DataModel/HypermediaContainer.ts | 12 +- src/JsonLd/mappings.ts | 6 + src/namespaces.ts | 1 + tests/JsonLd/JsonLdMetadataProvider.spec.ts | 115 ++++++++++-------- tests/JsonLd/input.json | 45 ++++--- 8 files changed, 127 insertions(+), 71 deletions(-) diff --git a/integration-tests/server/api/context.jsonld b/integration-tests/server/api/context.jsonld index fa8e7455..5fe09c01 100644 --- a/integration-tests/server/api/context.jsonld +++ b/integration-tests/server/api/context.jsonld @@ -44,6 +44,10 @@ "@type": "@vocab" }, "required": "hydra:required", + "collections": { + "@id": "hydra:collection", + "@type": "@id" + }, "member": { "@id": "hydra:member", "@type": "@id" diff --git a/src/DataModel/Collections/FilterableCollection.ts b/src/DataModel/Collections/FilterableCollection.ts index 0106d2e5..a86516f1 100644 --- a/src/DataModel/Collections/FilterableCollection.ts +++ b/src/DataModel/Collections/FilterableCollection.ts @@ -56,6 +56,19 @@ export default abstract class FilterableCollection { return null; } + /** + * Gets the last item of the collection or null if there are no items matching the criteria. + * @returns {T} + */ + public last(): T { + let result: T; + for (const item of this) { + result = item; + } + + return result; + } + /** * Filters the collection with a generic match evaluator. * @param matchEvaluator {Function} Match evaluation delegate. diff --git a/src/DataModel/Collections/TypesCollection.ts b/src/DataModel/Collections/TypesCollection.ts index 9a0c7021..03eb1db3 100644 --- a/src/DataModel/Collections/TypesCollection.ts +++ b/src/DataModel/Collections/TypesCollection.ts @@ -16,7 +16,7 @@ export default class TypesCollection extends Array { public constructor(...types: string[]); public constructor(types: any) { - super(...types); + super(...([...types] as string[]).filter((type, index, array) => array.indexOf(type) === index)); } /** diff --git a/src/DataModel/HypermediaContainer.ts b/src/DataModel/HypermediaContainer.ts index 2adc2d3f..5122bc39 100644 --- a/src/DataModel/HypermediaContainer.ts +++ b/src/DataModel/HypermediaContainer.ts @@ -35,10 +35,16 @@ export default class HypermediaContainer extends ResourceFilterableCollection ) { super(items); + const itemsArray = Array.from(items); + const explicitCollections: ICollection[] = itemsArray + .filter(control => control.type.contains(hydra.Collection)) + .map(control => control as ICollection); + const entryPointCollections: ICollection[] = + itemsArray + .filter(control => control.type.contains(hydra.EntryPoint)) + .map(control => Array.from((control as any).collections) as ICollection[])[0] || []; this.operations = operations; - this.collections = new ResourceFilterableCollection(Array.from(items).filter(control => - control.type.contains(hydra.Collection) - ) as ICollection[]); + this.collections = new ResourceFilterableCollection(explicitCollections.concat(entryPointCollections)); this.links = links; this.members = members instanceof ResourceFilterableCollection ? members : new ResourceFilterableCollection(members); diff --git a/src/JsonLd/mappings.ts b/src/JsonLd/mappings.ts index 07312add..829ea7c5 100644 --- a/src/JsonLd/mappings.ts +++ b/src/JsonLd/mappings.ts @@ -75,6 +75,12 @@ mappings[hydra.entrypoint] = { required: true, type: [hydra.ApiDocumentation as string] }; +mappings[hydra.collection] = { + default: (collections, processingState) => new ResourceFilterableCollection(collections), + propertyName: "collections", + required: true, + type: [hydra.EntryPoint as string] +}; mappings[hydra.template] = { default: "", propertyName: "template", diff --git a/src/namespaces.ts b/src/namespaces.ts index de6606c2..38557508 100644 --- a/src/namespaces.ts +++ b/src/namespaces.ts @@ -16,6 +16,7 @@ export let hydra = { BasicRepresentation: hydraNamespace + "BasicRepresentation", IriTemplate: hydraNamespace + "IriTemplate", + collection: hydraNamespace + "collection", member: hydraNamespace + "member", memberTemplate: hydraNamespace + "memberTemplate", totalItems: hydraNamespace + "totalItems", diff --git a/tests/JsonLd/JsonLdMetadataProvider.spec.ts b/tests/JsonLd/JsonLdMetadataProvider.spec.ts index de944257..526ec419 100644 --- a/tests/JsonLd/JsonLdMetadataProvider.spec.ts +++ b/tests/JsonLd/JsonLdMetadataProvider.spec.ts @@ -27,7 +27,7 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { describe("when parsing", () => { beforeEach( run(async () => { - this.response = returnOk("http://temp.uri/", inputJsonLd); + this.response = returnOk("http://temp.uri/api", inputJsonLd); this.result = await this.hypermediaProcessor.process(this.response, false); }) ); @@ -37,70 +37,85 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { }); it("should discover all collections", () => { - expect(this.result.hypermedia.collections.length).toBe(1); - expect(this.result.hypermedia.collections.first().iri).toMatch("/api/events$"); + expect(this.result.hypermedia.collections.length).toBe(2); + }); + + it("should discover people collection", () => { + expect(this.result.hypermedia.collections.first().iri).toMatch("/api/people$"); + }); + + it("should discover events collection", () => { + expect(this.result.hypermedia.collections.last().iri).toMatch("/api/events$"); }); it("should separate hypermedia", () => { expect(this.result.hypermedia).toBeLike([ { - iri: "http://temp.uri/api/events", - links: [ - { - baseUrl: "http://temp.uri/", - iri: "http://temp.uri/vocab/closed-events", - links: [], - operations: [], - relation: "http://temp.uri/vocab/closed-events", - target: { iri: "http://temp.uri/api/events/closed", type: [] }, - type: [hydra.Link] - }, - { - baseUrl: "http://temp.uri/", - iri: "http://www.w3.org/ns/hydra/core#first", - links: [], - operations: [], - relation: "http://www.w3.org/ns/hydra/core#first", - target: { iri: "http://temp.uri/api/events?page=1", type: [] }, - type: [hydra.Link] - }, + collections: [ { - baseUrl: "http://temp.uri/", - iri: "http://www.w3.org/ns/hydra/core#last", + iri: "http://temp.uri/api/people", links: [], operations: [], - relation: "http://www.w3.org/ns/hydra/core#last", - target: { iri: "http://temp.uri/api/events?page=9", type: [] }, - type: [hydra.Link] + type: [] }, { - baseUrl: "http://temp.uri/", - iri: "http://www.w3.org/ns/hydra/core#search", - links: [], + iri: "http://temp.uri/api/events", + links: [ + { + baseUrl: "http://temp.uri/api", + iri: "http://temp.uri/vocab/closed-events", + links: [], + operations: [], + relation: "http://temp.uri/vocab/closed-events", + target: { iri: "http://temp.uri/api/events/closed", type: [] }, + type: [hydra.Link] + }, + { + baseUrl: "http://temp.uri/api", + iri: "http://www.w3.org/ns/hydra/core#first", + links: [], + operations: [], + relation: "http://www.w3.org/ns/hydra/core#first", + target: { iri: "http://temp.uri/api/events?page=1", type: [] }, + type: [hydra.Link] + }, + { + baseUrl: "http://temp.uri/api", + iri: "http://www.w3.org/ns/hydra/core#last", + links: [], + operations: [], + relation: "http://www.w3.org/ns/hydra/core#last", + target: { iri: "http://temp.uri/api/events?page=9", type: [] }, + type: [hydra.Link] + }, + { + baseUrl: "http://temp.uri/api", + iri: "http://www.w3.org/ns/hydra/core#search", + links: [], + operations: [], + relation: "http://www.w3.org/ns/hydra/core#search", + target: null, + template: "http://temp.uri/api/events{?searchPhrase}", + type: [hydra.TemplatedLink] + } + ], + members: [ + { + iri: "http://temp.uri/api/events/1", + links: [], + operations: [], + type: [] + } + ], operations: [], - relation: "http://www.w3.org/ns/hydra/core#search", - target: null, - template: "http://temp.uri/api/events{?searchPhrase}", - type: [hydra.TemplatedLink] + totalItems: 1, + type: [hydra.Collection] } ], - members: [ - { - iri: "http://temp.uri/api/events/1", - links: [], - operations: [], - type: [] - } - ], - operations: [], - totalItems: 1, - type: [hydra.Collection] - }, - { - iri: "http://temp.uri/", + iri: "http://temp.uri/api", links: [], operations: [], - type: [] + type: [hydra.EntryPoint] } ]); }); diff --git a/tests/JsonLd/input.json b/tests/JsonLd/input.json index db68448a..6d1b1095 100644 --- a/tests/JsonLd/input.json +++ b/tests/JsonLd/input.json @@ -12,24 +12,35 @@ "@id": "some:named.graph", "@graph": [ { - "@id": "http://temp.uri/api/events", - "@type": "hydra:Collection", - "api:closed-events": "http://temp.uri/api/events/closed", - "hydra:search": { - "@type": "hydra:IriTemplate", - "hydra:template": "http://temp.uri/api/events{?searchPhrase}", - "hydra:mapping": { - "hydra:variable": "searchPhrase", - "hydra:property": "hydra:freetextQuery", - "hydra:variableRepresentation": "hydra:BasicVariableRepresentation", - "hydra:required": false + "@id": "http://temp.uri/api", + "@type": "hydra:EntryPoint", + "hydra:collection": [ + { + "@id": "http://temp.uri/api/people" + }, + { + "@id": "http://temp.uri/api/events", + "@type": "hydra:Collection", + "api:closed-events": "http://temp.uri/api/events/closed", + "hydra:search": { + "@type": "hydra:IriTemplate", + "hydra:template": "http://temp.uri/api/events{?searchPhrase}", + "hydra:mapping": { + "hydra:variable": "searchPhrase", + "hydra:property": "hydra:freetextQuery", + "hydra:variableRepresentation": "hydra:BasicVariableRepresentation", + "hydra:required": false + } + }, + "hydra:first": "http://temp.uri/api/events?page=1", + "hydra:last": "http://temp.uri/api/events?page=9", + "hydra:totalItems": 1, + "hydra:member": [ + { + "@id": "http://temp.uri/api/events/1" + } + ] } - }, - "hydra:first": "http://temp.uri/api/events?page=1", - "hydra:last": "http://temp.uri/api/events?page=9", - "hydra:totalItems": 1, - "hydra:member": [ - { "@id": "http://temp.uri/api/events/1" } ] }, { From 7a9184c87e7906a03377acfb7a1b077fd580d6f7 Mon Sep 17 00:00:00 2001 From: alien-mcl Date: Sun, 21 Jan 2018 21:19:35 +0100 Subject: [PATCH 3/6] Expanded support for hydra:collection to other that hydra:EntryPoint resources. --- src/DataModel/HypermediaContainer.ts | 2 +- src/DataModel/IHydraResource.ts | 9 +++++++++ src/DataModel/TemplatedLink.ts | 6 ++++++ src/DataModel/TemplatedOperation.ts | 5 +++++ src/JsonLd/linksExtractor.ts | 3 +++ src/JsonLd/mappings.ts | 3 +-- tests/JsonLd/JsonLdMetadataProvider.spec.ts | 7 +++++++ 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/DataModel/HypermediaContainer.ts b/src/DataModel/HypermediaContainer.ts index 5122bc39..2bc112ac 100644 --- a/src/DataModel/HypermediaContainer.ts +++ b/src/DataModel/HypermediaContainer.ts @@ -41,7 +41,7 @@ export default class HypermediaContainer extends ResourceFilterableCollection control as ICollection); const entryPointCollections: ICollection[] = itemsArray - .filter(control => control.type.contains(hydra.EntryPoint)) + .filter(control => !!(control as any).collections) .map(control => Array.from((control as any).collections) as ICollection[])[0] || []; this.operations = operations; this.collections = new ResourceFilterableCollection(explicitCollections.concat(entryPointCollections)); diff --git a/src/DataModel/IHydraResource.ts b/src/DataModel/IHydraResource.ts index e1d250d9..a52f2dd2 100644 --- a/src/DataModel/IHydraResource.ts +++ b/src/DataModel/IHydraResource.ts @@ -1,5 +1,7 @@ import LinksCollection from "./Collections/LinksCollection"; import OperationsCollection from "./Collections/OperationsCollection"; +import ResourceFilterableCollection from "./Collections/ResourceFilterableCollection"; +import { ICollection } from "./ICollection"; import { IResource } from "./IResource"; /** @@ -7,6 +9,13 @@ import { IResource } from "./IResource"; * @interface */ export interface IHydraResource extends IResource { + /** + * Gets collections exposed by that resource. + * @readonly + * @returns {ResourceFilterableCollection} + */ + readonly collections: ResourceFilterableCollection; + /** * Gets operations that can be performed on that resource. * @readonly diff --git a/src/DataModel/TemplatedLink.ts b/src/DataModel/TemplatedLink.ts index 1d3bfddf..01c02c18 100644 --- a/src/DataModel/TemplatedLink.ts +++ b/src/DataModel/TemplatedLink.ts @@ -2,7 +2,9 @@ import * as URITemplate from "uri-templates"; import { hydra } from "../namespaces"; import LinksCollection from "./Collections/LinksCollection"; import OperationsCollection from "./Collections/OperationsCollection"; +import ResourceFilterableCollection from "./Collections/ResourceFilterableCollection"; import TypesCollection from "./Collections/TypesCollection"; +import { ICollection } from "./ICollection"; import { IIriTemplate } from "./IIriTemplate"; import { ILink } from "./ILink"; import { IResource } from "./IResource"; @@ -19,6 +21,8 @@ export default class TemplatedLink implements ITemplatedLink { public readonly baseUrl: string; + public readonly collections: ResourceFilterableCollection; + public readonly iri: string; public readonly relation: string; @@ -39,6 +43,7 @@ export default class TemplatedLink implements ITemplatedLink { public constructor(linkResource: ILink, template: IIriTemplate) { const types = [...linkResource.type].filter(type => type !== hydra.Link).concat([hydra.TemplatedLink]); this.baseUrl = linkResource.baseUrl; + this.collections = linkResource.collections; this.iri = linkResource.iri; this.relation = linkResource.relation; this.target = null; @@ -55,6 +60,7 @@ export default class TemplatedLink implements ITemplatedLink { const target = targetUri.match(/^[a-zA-Z][a-zA-Z0-9_]*:/) ? targetUri : new URL(targetUri, this.baseUrl).toString(); return { baseUrl: this.baseUrl, + collections: this.collections, iri: `_:blankLink${++TemplatedLink.id}`, links: this.links, operations: this.operations, diff --git a/src/DataModel/TemplatedOperation.ts b/src/DataModel/TemplatedOperation.ts index 6d41b876..2e6501d3 100644 --- a/src/DataModel/TemplatedOperation.ts +++ b/src/DataModel/TemplatedOperation.ts @@ -5,6 +5,7 @@ import OperationsCollection from "./Collections/OperationsCollection"; import ResourceFilterableCollection from "./Collections/ResourceFilterableCollection"; import TypesCollection from "./Collections/TypesCollection"; import { IClass } from "./IClass"; +import { ICollection } from "./ICollection"; import { IIriTemplate } from "./IIriTemplate"; import { IOperation } from "./IOperation"; import { IResource } from "./IResource"; @@ -21,6 +22,8 @@ export default class TemplatedOperation implements ITemplatedOperation { public readonly baseUrl: string; + public readonly collections: ResourceFilterableCollection; + public readonly iri: string; public readonly type: TypesCollection; @@ -43,6 +46,7 @@ export default class TemplatedOperation implements ITemplatedOperation { public constructor(operationResource: IOperation, template: IIriTemplate) { const types = [...operationResource.type].concat([hydra.Operation, hydra.IriTemplate]); this.baseUrl = operationResource.baseUrl; + this.collections = operationResource.collections; this.iri = `_:bnode${++TemplatedOperation.id}`; this.type = new TypesCollection(types.filter((type, index) => types.indexOf(type) === index)); this.method = operationResource.method; @@ -60,6 +64,7 @@ export default class TemplatedOperation implements ITemplatedOperation { const target = targetUri.match(/^[a-zA-Z][a-zA-Z0-9_]*:/) ? targetUri : new URL(targetUri, this.baseUrl).toString(); return { baseUrl: this.baseUrl, + collections: this.collections, expects: this.expects, iri: `_:operation${++TemplatedOperation.id}`, links: this.links, diff --git a/src/JsonLd/linksExtractor.ts b/src/JsonLd/linksExtractor.ts index 54f20cfe..452d7889 100644 --- a/src/JsonLd/linksExtractor.ts +++ b/src/JsonLd/linksExtractor.ts @@ -1,6 +1,8 @@ import LinksCollection from "../DataModel/Collections/LinksCollection"; import OperationsCollection from "../DataModel/Collections/OperationsCollection"; +import ResourceFilterableCollection from "../DataModel/Collections/ResourceFilterableCollection"; import TypesCollection from "../DataModel/Collections/TypesCollection"; +import { ICollection } from "../DataModel/ICollection"; import TemplatedLink from "../DataModel/TemplatedLink"; import { hydra } from "../namespaces"; @@ -33,6 +35,7 @@ export const linksExtractor = (resources, processingState) => { processingState.forbiddenHypermedia.push(predicate); let link = { baseUrl: processingState.baseUrl, + collections: new ResourceFilterableCollection([]), iri: predicate, links: new LinksCollection([]), operations: new OperationsCollection([]), diff --git a/src/JsonLd/mappings.ts b/src/JsonLd/mappings.ts index 829ea7c5..d90540f3 100644 --- a/src/JsonLd/mappings.ts +++ b/src/JsonLd/mappings.ts @@ -78,8 +78,7 @@ mappings[hydra.entrypoint] = { mappings[hydra.collection] = { default: (collections, processingState) => new ResourceFilterableCollection(collections), propertyName: "collections", - required: true, - type: [hydra.EntryPoint as string] + required: true }; mappings[hydra.template] = { default: "", diff --git a/tests/JsonLd/JsonLdMetadataProvider.spec.ts b/tests/JsonLd/JsonLdMetadataProvider.spec.ts index 526ec419..3b32815a 100644 --- a/tests/JsonLd/JsonLdMetadataProvider.spec.ts +++ b/tests/JsonLd/JsonLdMetadataProvider.spec.ts @@ -53,16 +53,19 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { { collections: [ { + collections: [], iri: "http://temp.uri/api/people", links: [], operations: [], type: [] }, { + collections: [], iri: "http://temp.uri/api/events", links: [ { baseUrl: "http://temp.uri/api", + collections: [], iri: "http://temp.uri/vocab/closed-events", links: [], operations: [], @@ -72,6 +75,7 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { }, { baseUrl: "http://temp.uri/api", + collections: [], iri: "http://www.w3.org/ns/hydra/core#first", links: [], operations: [], @@ -81,6 +85,7 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { }, { baseUrl: "http://temp.uri/api", + collections: [], iri: "http://www.w3.org/ns/hydra/core#last", links: [], operations: [], @@ -90,6 +95,7 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { }, { baseUrl: "http://temp.uri/api", + collections: [], iri: "http://www.w3.org/ns/hydra/core#search", links: [], operations: [], @@ -101,6 +107,7 @@ describe("Given instance of the JsonLdHypermediaProcessor class", () => { ], members: [ { + collections: [], iri: "http://temp.uri/api/events/1", links: [], operations: [], From f73bc553efaaf948b6a7bc6e31ed624cc3a8fa0b Mon Sep 17 00:00:00 2001 From: alien-mcl Date: Wed, 31 Jan 2018 22:40:29 +0100 Subject: [PATCH 4/6] Minor changes after code review --- src/DataModel/Collections/FilterableCollection.ts | 2 +- src/DataModel/HypermediaContainer.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DataModel/Collections/FilterableCollection.ts b/src/DataModel/Collections/FilterableCollection.ts index a86516f1..c01ecf4b 100644 --- a/src/DataModel/Collections/FilterableCollection.ts +++ b/src/DataModel/Collections/FilterableCollection.ts @@ -61,7 +61,7 @@ export default abstract class FilterableCollection { * @returns {T} */ public last(): T { - let result: T; + let result: T = null; for (const item of this) { result = item; } diff --git a/src/DataModel/HypermediaContainer.ts b/src/DataModel/HypermediaContainer.ts index 2bc112ac..943bb883 100644 --- a/src/DataModel/HypermediaContainer.ts +++ b/src/DataModel/HypermediaContainer.ts @@ -39,12 +39,12 @@ export default class HypermediaContainer extends ResourceFilterableCollection control.type.contains(hydra.Collection)) .map(control => control as ICollection); - const entryPointCollections: ICollection[] = + const availableCollections: ICollection[] = itemsArray .filter(control => !!(control as any).collections) .map(control => Array.from((control as any).collections) as ICollection[])[0] || []; this.operations = operations; - this.collections = new ResourceFilterableCollection(explicitCollections.concat(entryPointCollections)); + this.collections = new ResourceFilterableCollection(explicitCollections.concat(availableCollections)); this.links = links; this.members = members instanceof ResourceFilterableCollection ? members : new ResourceFilterableCollection(members); From ce1fb151a98785f31ec4a7fc679d197072bcf842 Mon Sep 17 00:00:00 2001 From: alien-mcl Date: Wed, 7 Mar 2018 21:03:04 +0100 Subject: [PATCH 5/6] Minor changes after code review --- src/DataModel/HypermediaContainer.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/DataModel/HypermediaContainer.ts b/src/DataModel/HypermediaContainer.ts index 943bb883..289effad 100644 --- a/src/DataModel/HypermediaContainer.ts +++ b/src/DataModel/HypermediaContainer.ts @@ -3,6 +3,7 @@ import LinksCollection from "./Collections/LinksCollection"; import OperationsCollection from "./Collections/OperationsCollection"; import ResourceFilterableCollection from "./Collections/ResourceFilterableCollection"; import { ICollection } from "./ICollection"; +import { IHydraResource } from "./IHydraResource"; import { IHypermediaContainer } from "./IHypermediaContainer"; import { IResource } from "./IResource"; @@ -36,15 +37,18 @@ export default class HypermediaContainer extends ResourceFilterableCollection control.type.contains(hydra.Collection)) .map(control => control as ICollection); - const availableCollections: ICollection[] = - itemsArray - .filter(control => !!(control as any).collections) - .map(control => Array.from((control as any).collections) as ICollection[])[0] || []; + const linkedCollections: ICollection[] = Array.prototype.concat( + ...itemsArray + .filter((control: any) => !!control.collections) + .map((control: IHydraResource) => Array.from(control.collections)) + ); this.operations = operations; - this.collections = new ResourceFilterableCollection(explicitCollections.concat(availableCollections)); + this.collections = new ResourceFilterableCollection( + explicitelyTypedCollections.concat(linkedCollections) + ); this.links = links; this.members = members instanceof ResourceFilterableCollection ? members : new ResourceFilterableCollection(members); From 9d7781dc8f6e972f3908c41f84327d36e6fb8815 Mon Sep 17 00:00:00 2001 From: alien-mcl Date: Sun, 18 Mar 2018 19:53:26 +0100 Subject: [PATCH 6/6] Typo change --- integration-tests/server/api/context.jsonld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/server/api/context.jsonld b/integration-tests/server/api/context.jsonld index 5fe09c01..cf1450c8 100644 --- a/integration-tests/server/api/context.jsonld +++ b/integration-tests/server/api/context.jsonld @@ -44,7 +44,7 @@ "@type": "@vocab" }, "required": "hydra:required", - "collections": { + "collection": { "@id": "hydra:collection", "@type": "@id" },