Skip to content

Commit

Permalink
Add support for IriTemplate and IriTemplateMapping
Browse files Browse the repository at this point in the history
  • Loading branch information
alien-mcl authored and lanthaler committed Feb 6, 2018
1 parent 17deda5 commit af85b10
Show file tree
Hide file tree
Showing 19 changed files with 401 additions and 161 deletions.
26 changes: 26 additions & 0 deletions integration-tests/HydraClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,32 @@ describe("Having a Hydra client", () => {
expect(matchingEvents).not.toBeNull();
});
});

describe("and then using a custom arbitrarily pointed templated operation", () => {
beforeEach(
run(async () => {
const link = this.events.hypermedia.links
.withRelationOf("http://example.com/vocab#filter")
.withTemplate()
.first()
.expandTarget(_ =>
_.withProperty("http://schema.org/name")
.havingValueOf("name")
.withVariable("eventDescription")
.havingValueOf("description")
);
this.filteringResult = await this.client.getResource(link);
})
);

it("should obtain matching events", () => {
const matchingEvents = this.filteringResult.hypermedia
.where(item => item.iri.match("/api/events$") && item.type.contains(hydra.Collection))
.first();
expect(matchingEvents).toBeDefined();
expect(matchingEvents).not.toBeNull();
});
});
});

describe("and then obtaining people", () => {
Expand Down
11 changes: 8 additions & 3 deletions integration-tests/server/api/context.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,18 @@
"@id": "hydra:template",
"@type": "xsd:string"
},
"mapping": {
"mappings": {
"@id": "hydra:mapping",
"@type": "@id"
},
"variable": {
"@id": "hydra:variable",
"@type": "@id"
"@type": "xsd:string"
},
"variableRepresentation": "hydra:variableRepresentation",
"search": {
"@id": "hydra:search"
"@id": "hydra:search",
"@type": "@id"
},
"Event": {
"@id": "schema:Event",
Expand All @@ -85,6 +86,10 @@
"endDate": {
"@id": "schema:endDate",
"@type": "xsd:dateTime"
},
"examplevocab:filter": {
"@id": "examplevocab:filter",
"@type": "@id"
}
}
}
81 changes: 54 additions & 27 deletions integration-tests/server/api/events.jsonld
Original file line number Diff line number Diff line change
@@ -1,35 +1,62 @@
{
"@context": "/api/context.jsonld",
"@id": "/api/events",
"@type": ["hydra:Collection", "hydra:Resource"],
"member": [
"@graph": [
{
"@id": "/api/events/1",
"@type": "schema:Event"
"@id": "/api/events",
"@type": ["hydra:Collection", "hydra:Resource"],
"member": [
{
"@id": "/api/events/1",
"@type": "schema:Event"
},
{
"@id": "/api/events/2",
"@type": "schema:Event"
},
{
"@id": "/api/events/3",
"@type": "schema:Event"
}
],
"search": {
"@type": "hydra:IriTemplate",
"template": "/api/events{?searchText}",
"mappings": {
"@type": "hydra:IriTemplateMapping",
"variable": "searchText",
"property": "hydra:freetextQuery",
"variableRepresentation": "hydra:BasicRepresentation"
}
},
"examplevocab:filter": {
"@type": "hydra:IriTemplate",
"template": "/api/events{?eventName,eventDescription}",
"mappings": [
{
"@type": "hydra:IriTemplateMapping",
"variable": "eventName",
"property": "schema:name",
"variableRepresentation": "hydra:BasicRepresentation"
},
{
"@type": "hydra:IriTemplateMapping",
"variable": "eventDescription",
"property": "schema:description",
"variableRepresentation": "hydra:BasicRepresentation"
}
]
},
"operation": [
{
"@type": ["hydra:Operation", "schema:CreateAction", "schema:AddAction"],
"method": "POST",
"expects": "schema:Event"
}
]
},
{
"@id": "/api/events/2",
"@type": "schema:Event"
},
{
"@id": "/api/events/3",
"@type": "schema:Event"
}
],
"search": {
"@type": "hydra:IriTemplate",
"template": "/api/events{?searchText}",
"mappings": {
"variable": "searchText",
"property": "hydra:freetextQuery",
"variableRepresentation": "hydra:BasicRepresentation"
}
},
"operation": [
{
"@type": ["hydra:Operation", "schema:CreateAction", "schema:AddAction"],
"method": "POST",
"expects": "schema:Event"
"@id": "examplevocab:filter",
"@type": "hydra:TemplatedLink"
}
]
}
2 changes: 1 addition & 1 deletion integration-tests/server/api/people.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@type": "hydra:IriTemplate",
"template": "/api/people/{name}",
"variableRepresentation": "hydra:BasicRepresentation",
"mapping": [
"mappings": [
{
"@type": "hydra:IriTemplateMapping",
"variable": "name",
Expand Down
19 changes: 14 additions & 5 deletions src/DataModel/Collections/MappingsCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,23 @@ export default class MappingsCollection extends ResourceFilterableCollection<IIr

/**
* Obtains a collection of mappings for a given variable name.
* @param variableName {string} Variable name.
* @param {string} variableName Variable name.
* @returns {IMappingsCollection}
*/
public ofVariableName(variableName: string): MappingsCollection {
return (this.narrowFiltersWith<IIriTemplateMapping>(
"variable",
value => value.variable === variableName
) as any) as MappingsCollection;
return this.narrowFiltersWith("variable", variableName) as MappingsCollection;
}

/**
* Obtains a collection of mappings for a given predicate.
* @param {string} property Predicate IRI.
* @returns {IMappingsCollection}
*/
public ofProperty(property: string): MappingsCollection {
return this.narrowFiltersWith<IIriTemplateMapping>(
"property",
value => value.property.iri === property
) as MappingsCollection;
}

protected createInstance(items: Iterable<IIriTemplateMapping>): MappingsCollection {
Expand Down
18 changes: 2 additions & 16 deletions src/DataModel/IOperation.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import ResourceFilterableCollection from "./Collections/ResourceFilterableCollection";
import { IClass } from "./IClass";
import { IHydraResource } from "./IHydraResource";
import { IResource } from "./IResource";
import { IPointingResource } from "./IPointingResource";

/**
* Describes an abstract Hydra operation.
* @interface
*/
export interface IOperation extends IHydraResource {
/**
* Gets a base URL that can be used to resolve target in case it is relative.
* @readonly
* @returns {string}
*/
readonly baseUrl: string;

/**
* Gets a target URL to be called.
* @readonly
* @returns {IResource}
*/
readonly target: IResource;

export interface IOperation extends IHydraResource, IPointingResource {
/**
* Gets a method to be used for the call.
* @readonly
Expand Down
22 changes: 22 additions & 0 deletions src/DataModel/IPointingResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IHydraResource } from "./IHydraResource";
import { IResource } from "./IResource";

/**
* Provides an abstract description of a resource that points to another one.
* @interface
*/
export interface IPointingResource extends IHydraResource {
/**
* Gets a base URL that can be used to resolve target in case it is relative.
* @readonly
* @returns {string}
*/
readonly baseUrl: string;

/**
* Gets a target URL to be called.
* @readonly
* @returns {IResource}
*/
readonly target: IResource;
}
11 changes: 2 additions & 9 deletions src/DataModel/ITemplatedLink.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { ILink } from "./ILink";
import { IResource } from "./IResource";
import { ITemplatedResource } from "./ITemplatedResource";

/**
* Provides a link that can has an URI template.
* @interface
*/

export interface ITemplatedLink extends ILink {
/**
* Expands an URI template with given variables.
* @param templateVariables {{ [name: string]: string }} Template variables with values.
* @returns {IResource}
*/
expandTarget(templateVariables: { [name: string]: string }): IResource;
}
export interface ITemplatedLink extends ILink, ITemplatedResource<ILink> {}
10 changes: 2 additions & 8 deletions src/DataModel/ITemplatedOperation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { IOperation } from "./IOperation";
import { ITemplatedResource } from "./ITemplatedResource";

/**
* Describes an {@link IOperation} that uses an URI template to point to the target of the request.
* @interface
*/
export interface ITemplatedOperation extends IOperation {
/**
* Expands an URI template with given variables.
* @param templateVariables {{ [name: string]: string }} Template variables with values.
* @returns {IOperation}
*/
expandTarget(templateVariables: { [name: string]: string }): IOperation;
}
export interface ITemplatedOperation extends IOperation, ITemplatedResource<IOperation> {}
20 changes: 20 additions & 0 deletions src/DataModel/ITemplatedResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { IPointingResource } from "./IPointingResource";

export interface IDictionary {
[name: string]: string;
}
export type MappingBuilder = (MappingsBuilder) => void;

/**
* Provides an abstract description of a resource with expandable template.
* @interface
*/
export interface ITemplatedResource<T extends IPointingResource> extends IPointingResource {
/**
* Expands an URI template with given variables.
* @param {{ [name: string]: string } | (MappingsBuilder) => void} mappedVariables Template variables with values or
* {@link MappingsBuilder}.
* @returns {T}
*/
expandTarget(mappedVariables: IDictionary | MappingBuilder): T;
}
34 changes: 34 additions & 0 deletions src/DataModel/MappingsBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import MappingsCollection from "./Collections/MappingsCollection";
import PropertyMapping from "./PropertyMapping";

export default class MappingsBuilder {
private readonly mappings: MappingsCollection;
private readonly result: { [name: string]: string };

public constructor(mappings: MappingsCollection) {
this.mappings = mappings;
this.result = {};
}

public withProperty(property: string): PropertyMapping {
const mapping = this.mappings.ofProperty(property).first();
if (!mapping) {
throw new Error(`Invalid mapping of property '${property}.`);
}

return new PropertyMapping(this, this.result, mapping);
}

public withVariable(variableName: string): PropertyMapping {
const mapping = this.mappings.ofVariableName(variableName).first();
if (!mapping) {
throw new Error(`Invalid mapping of variable name mapping '${variableName}.`);
}

return new PropertyMapping(this, this.result, mapping);
}

public complete(): { [name: string]: string } {
return this.result;
}
}
19 changes: 19 additions & 0 deletions src/DataModel/PropertyMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IIriTemplateMapping } from "./IIriTemplateMapping";
import MappingsBuilder from "./MappingsBuilder";

export default class PropertyMapping {
private readonly builder: MappingsBuilder;
private readonly result: { [name: string]: string };
private readonly mapping: IIriTemplateMapping;

public constructor(builder: MappingsBuilder, result: { [name: string]: string }, mapping: IIriTemplateMapping) {
this.builder = builder;
this.result = result;
this.mapping = mapping;
}

public havingValueOf(value: string): MappingsBuilder {
this.result[this.mapping.variable] = value;
return this.builder;
}
}
Loading

0 comments on commit af85b10

Please sign in to comment.