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

Reference client kick-off #1

Merged
merged 2 commits into from
Jun 27, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.idea
.vs
bin
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

38 changes: 38 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module.exports = function(config) {
config.set({
basePath: "",
frameworks: ["jasmine", "sinon", "jasmine-sinon", "source-map-support", "karma-typescript"],
files: [
{ pattern: "src/**/*.ts", included: true },
{ pattern: "testing/**/*.ts", included: true },
{ pattern: "tests/**/*.spec.ts", included: true }
],
exclude: [
"jsonld-request"
],
preprocessors: {
"**/*.ts": ["karma-typescript"]
},
karmaTypescriptConfig: {
tsconfig: "tsconfig.json",
coverageOptions: {
instrumentation: false
},
bundlerOptions: {
entrypoints: /\.spec\.ts$/,
exclude: [
"jsonld-request",
"pkginfo"
]
}
},
reporters: ["progress", "karma-typescript"],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ["Chrome"],
singleRun: false,
concurrency: Infinity
})
};
28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "hydraclient.js",
"version": "0.0.1",
"scripts": {
"test": "node node_modules/karma/bin/karma start",
"test-once": "node node_modules/karma/bin/karma start --single-run"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to Hydra, I'd swap these scripts. Travis will run npm test and it makes more sense that it was the single run script

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no opinion here. Whichever is better from a developer point of view (not a tool point of view)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since start and test are standard script commands, I'd say just use them. It's also possible to chain script commands together, so I'd do the following:

"scripts": {
    "start": "node node_modules/karma/bin/karma start",
    "test": "npm start -- --single-run"
}

Copy link

@kartikadur kartikadur Jun 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asbjornu +1 for chaining.

I would suggest keeping test and test-once as names since they are action specific and related by name.

Not sure if this is relevant but I remember reading that the watch parameter also need to be specified as false for the single run command.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to 'test' (single run) and 'live-test' (for continuous running test session)

},
"dependencies": {
"jsonld": "^0.4.12",
"typescript": "^2.3.3",
"whatwg-fetch": "^2.0.3"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got someone asking in "my" heracles to use isomorphic-fetch so that the client can be used both in browser and Node. Let's do that from get-go.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with isomorphic-fetch

},
"devDependencies": {
"@types/jasmine": "^2.5.47",
"@types/node": "^7.0.21",
"@types/sinon": "^2.2.2",
"jasmine": "^2.6.0",
"jasmine-sinon": "^0.4.0",
"karma": "^1.7.0",
"karma-chrome-launcher": "^2.1.1",
"karma-jasmine": "^1.1.0",
"karma-jasmine-sinon": "^1.0.4",
"karma-sinon": "^1.0.5",
"karma-source-map-support": "^1.2.0",
"karma-typescript": "^3.0.2",
"sinon": "^2.2.0"
}
}
30 changes: 30 additions & 0 deletions src/ApiDocumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {IApiDocumentation} from "./DataModel/IApiDocumentation";
import {IData} from "./DataModel/IData";
import {IClass} from "./DataModel/IClass";
import {IOperation} from "./DataModel/IOperation";
import HydraClient from "./HydraClient";

export default class ApiDocumentation implements IApiDocumentation
{
public supportedClasses: Array<IClass>;
public supportedOperations: Array<IOperation>;
public client: HydraClient;
public entryPoint: string;

public async getEntryPoint(): Promise<IData>
{
let response = await window.fetch(this.entryPoint);
if (response.status !== 200)
{
throw new Error(HydraClient.invalidResponse + response.status);
}

let metadataProvider = this.client.getMetadataProvider(response);
if (!metadataProvider)
{
throw new Error(HydraClient.responseFormatNotSupported);
}

return await metadataProvider.parse(response, true);
}
}
9 changes: 9 additions & 0 deletions src/DataModel/IApiDocumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {IMetadataDescription} from "./IMetadataDescription";
import {IData} from "./IData";

export interface IApiDocumentation extends IMetadataDescription
{
entryPoint: string;

getEntryPoint(): Promise<IData>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like how this is the functionality of the documentation. I treat the ApiDocumentation itself as a resource. Besides, this smells like it would violate Single Responsibility Principle

What would you think about smth like

IHydraApi api = new HydraApi("http://entrypoint");

IApiDocumentation docs = api.getApidDocumentation();
IResource entry = api.getEntryPoint();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the Single Responsibility Principle - I don't think it would be a violation - It's a functional inheritance and actual implementation could be a simple wrapper on top of an actual implementation.

Indeed there is an inconsistency in use cases - some places uses method chaining, i.e.:

var operation = client.get("http://example.com")
    .getApiDocumentation()
    .getEntryPoint()

And some other are using client as an invocation facility for operations, i.e.:

var operation = client.get("http://example.com")
    .getApiDocumentation()
    .getEntryPoint()
    .getOperations()
    .filter(op => op.method == "POST" && op.expects == "Event")[0];
client.invoke(operation, event);

I'm not sure which one is better. The latter seems cleaner, but it would force client to do quite a lot (i.e. make distinction on how to work with simple Url string, Link, Iri template or an operation).

As for your example, I wouldn't like to see developers create instances - I'd prefer our API to do it on it's own so developers don't need to bother about real implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't like to see developers create instances

Funny you'd say that. Previously I didn't actually implement it that way ;). Now that I look again at your code, maybe I was misjudging it a little.

My actual point is that ApiDocumentation seems to work both as a Hydra client and the documentation. I guess I'd simply move all the metadata business to this.client and maybe that's it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I'm torn apart - now I somehow tend to stick to that latter approach where client does all the stuff - I'm just worried that the client would be overloaded with logic.
The thing is that code equivalent following that approach:

var api = client.get("http://example.com");
var apiDocumentation = client.get(api.getApiDocumentationUrl());
var entryPoint = client.get(apiDocumentation.entryPoint);
var operation = entryPoint
    .filter(op => op.method == "POST" && op.expects == "Event")[0];
client.invoke(operation, event);

looks a bit messy

Copy link
Contributor

@tpluscode tpluscode Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, so procedural. Definitely not what I had in mind ;). But read again and notice that I meant moving the implementation details from the ApiDocumentation class. My idea was that it shouldn't care about fetch and metadataProvider etc. It would simply be

function getEntryPoint() {
  return this.client.getResource(this.entryPoint);
}

Otherwise the snippets in you comment above look good to me. Well, maybe I'd make the operation more self-aware, but that's a different discussion

var operation = {};
var event;

operation.invoke(event);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not so different. For simple Urls we'll need to call client's method, but for strongly typed resources we will use their methods - it's not really consistent.
As for the implementation hint - I had to be really tired when I was writing it :). It's so obvious this should be client's method called here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my experience in implementing hypermedia clients with System.Net.HttpClient, I find that having a central infrastructural and generic Client class that deals with HTTP communication, error handling, serialization, deserialization, etc., and then passing it around to the different specifics of the library works well.

Equivalently, I think it's wise to make ApiDocumentation rely on Client to do the heavy lifting and then just applying the specifics within the ApiDocumentation class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'ApiDocumentation; is now light and depends on a newly introduced HydraClient.getResource method,

}
5 changes: 5 additions & 0 deletions src/DataModel/IClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {IResource} from "./IResource";

export interface IClass extends IResource
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this used for? To represent hydra:Class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, someday when we'll reach that point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I don't think the code needs (or even should) match the representations on the wire 1:1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the wire - indeed. But I think client's model should be rooted in hydra's model somehow.

{
}
6 changes: 6 additions & 0 deletions src/DataModel/IData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {IMetadata} from "./IMetadata";

export interface IData extends Object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this and IMetadata. They seems too generic and separated from the Hydra terminology. I find using such disconnected terms confusing.

For IData, did you mean something like IHydraApi?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By IData i meant raw data. The thing that actually client wanted. Metadata - hypermedia controls, data structure description is something that helps a client to understand what it can/needs to do.
I'm open for naming suggestions. Hypermedia controls is to narrow for me (i.e. for structure definitions).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't like to see developers create instances

I think the client wants resource representations. What if instead IData we used IResource as the common denominator?

And metadata? I think I like the idea. You wat to separate the controls from the actual representation, right? A place to put operations, etc? So what if it was called IHypermedia or IControls (or IHypermediaControls?).

var resource = client.getResource('http://example.com');

resource.controls.getApiDocumentation();
resource.controls.getOperations();
resource.controls.getLinks();

Copy link
Member Author

@alien-mcl alien-mcl Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed I'd love to have raw data and hydra stuff somehow separated. As for the IData replacements - IResource is already used to denote actual resource. I wouldn't like to add a metadata property there. Bu I see direction your thoughts are going - I need to give it some thoughs.
As for metadata - maybe IHypermedia. I've got feeling that it's still to narrow as there may be something that doesn't qualify.

Copy link
Contributor

@tpluscode tpluscode Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asbjornu thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't find this that much of an issue. Sure, you get stuff mixed a little but that's why these are representations and not resources. Hypermedia is part of the representation, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library doesn't know what higher level libraries, apps, UIs want to do with the data so I wouldn't introduce such (leaky) separations/classifications here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this separation might not come easily. I agree that library doesn't know what higher level libraries, apps and UIs want to do with the data. I still think that client should know what is a hypermedia control - it's supposed to consume those.
Anyway - from API point of view - client should expose somehow both the payload (separated or not) and hypermedia controls it discovers within that payload.
@tpluscode example is a good one in that matter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundling hypermedia controls into a property seems like a clean approach. 👍 But what are they, exactly? Affordances? Operations? Actions? Controls? Naming things is hard.

Copy link
Member Author

@alien-mcl alien-mcl Jun 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed with IWebResource. I wanted to leave IResource to match the term used in the Hydra spec. The metadata property is renamed to hypermedia.

{
metadata: Array<IMetadata>;
}
6 changes: 6 additions & 0 deletions src/DataModel/IMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import HydraClient from "../HydraClient";

export interface IMetadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd get rid of this class for the reasons mentioned above

Copy link
Member Author

@alien-mcl alien-mcl Jun 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is still here, but I got rid of IMetadataDescriptor. This one also got renamed.

{
client: HydraClient;
}
11 changes: 11 additions & 0 deletions src/DataModel/IMetadataDescription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {IMetadata} from "./IMetadata";
import {IClass} from "./IClass";
import {IOperation} from "./IOperation";
import HydraClient from "../HydraClient";

export interface IMetadataDescription extends IMetadata
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to the above. Hydra specification doesn't speak about any metadata anywhere. The properties below suggest to me that maybe this would actually be the IApiDocumentation interface? And the current IApiDocumentation could become simply IApi?

I'm sorry I contradict earlier comments but I'm coming up with this stuff as I go through and these are my thoughts :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem - that's why it's a PR - we can discuss it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to what @tpluscode said

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one was removed

{
supportedClasses: Array<IClass>;

supportedOperations: Array<IOperation>;
}
30 changes: 30 additions & 0 deletions src/DataModel/IMetadataProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {IMetadataDescription} from "./IMetadataDescription";
import "whatwg-fetch";
import {IData} from "./IData";

/**
* @interface Describes an abstract meta-data providing facility which translates from raw @link Response to an abstract data model.
*/
export interface IMetadataProvider
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate a bit on why this class is needed? What is the abstract data model? IData and IMetadata? Something else?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to introduce an intermediate layer that would isolate the client and it's hypermedia controls internal data model from actual RDF serialization received. This would be a job for a IMetadataProvider to make that translation. In case of JSON-LD it would be i.e. framing.
As for the IData and IHypermedia - it is an attempt to isolate raw data that the client expected from the hypermedia controls so these can be easily used in the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the general idea of separation. Although I'm anxious to see how framing alone will be enough. I've been there too and I had to resort to actual triple processing as an intermediate processing step

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well - framing alone won't do it. There is already a very simple attempt to that topic in JsonLdMetadataProvider.ts (i.e. parseMetadata)

{
/**
* Gets supported media types.
* @returns {Array<string>}
*/
supportedMediaTypes: Array<string>;

/**
* Parses a given raw response.
* @param {Response} response Raw fetch response holding data to be parsed.
* @returns {Promise<IData>}
*/
parse(response: Response): Promise<IData>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it really parse? How about process or provide?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turtle implementation would actually parse it to get a JavaScript compatible objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but I think that parsing is just part of what IMetadataProvider does, is all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe process - provide is already in the name of the interface thus it is also part of what it doas.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed with IHypermediaProcessor. Method is now process.


/**
* Parses a given raw response.
* @param {Response} response Raw fetch response holding data to be parsed.
* @param {boolean} removeFromPayload Instructs whether to remove the metadata from the response's body or not.
* @returns {Promise<IData>}
*/
parse(response: Response, removeFromPayload: boolean): Promise<IData>;
}
6 changes: 6 additions & 0 deletions src/DataModel/IOperation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {IResource} from "./IResource";

export interface IOperation extends IResource
{

}
5 changes: 5 additions & 0 deletions src/DataModel/IResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface IResource
{
"@id": string;
"@type"?: Array<string>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting. I've nothing against @ but I think your intention was to make the representation serialization-agnostic, right? This seems clearly JSON-LD-inspired.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - I don't like @id either. The issue is that it's not something I can do with i.e. JSON-LD framing. Initially I had here an iri property.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@alien-mcl alien-mcl Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

He, good point. Didn't try it.
Still, it's a matter of a good replacement:
@id : iri (id or url will be already used in most cases)
@type : ? (type may be used as well, class is usually a keyword and may cause mayhem)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we actually use properties here and not methods? The programming interface doesn't need to match what's send on the wire

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being just pragmatic - I'd prefer to have resource's identifier as a property. Also, we'd need to extend every single JSON object from the payload with that method's implementation - it's a drag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something you'd still want to do to make a property read-only. A getter method would be just the same

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we'd need to extend every single JSON object from the payload with that method's implementation - it's a drag.

I think this is actually a deeper question. Do we want to use JSON-LD as data representation? Both internally and accessible through Heracles' interfaces? If not, the point above is moot. If yes, this should be made explicit.

Copy link
Member Author

@alien-mcl alien-mcl Jun 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No - currently I'm using JSON-LD framing to transform hypermedia controls I discover to an internal model. It is rooted in the hydra's structure, but still it is supposed to be separated from the JSON-LD serialization.
I also just don't want to parse JSON-LD too much as it is still JavaScript object which can be used with little modifications.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properties renamed to iri and isA (anyone got better name for the latter one?)

}
112 changes: 112 additions & 0 deletions src/DataModel/JsonLd/JsonLdMetadataProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import HydraClient from "../../HydraClient";
import {IMetadataProvider} from "../IMetadataProvider";
import {IData} from "../IData";
import {hydra} from "../../namespaces";
const jsonLd = require("jsonld").promises;
const context = require("./context.json");

export default class JsonLdMetadataProvider implements IMetadataProvider
{
private static _mediaTypes = ["application/json+ld"];
private static _id = 0;

static initialize()
{
HydraClient.registerMetadataProvider(new JsonLdMetadataProvider());
}

public get supportedMediaTypes(): Array<string>
{
return JsonLdMetadataProvider._mediaTypes;
}

public async parse(response: Response, removeFromPayload: boolean = false): Promise<IData>
{
let payload = await response.json();
let expanded = await jsonLd.flatten(payload);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/expanded/flattened/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep - forgot to replace variable name while experimenting with JSON-LD api.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to flattened.

let metadata = JsonLdMetadataProvider.parseMetadata(expanded, new Array<any>(), removeFromPayload);
let result = await jsonLd.frame(metadata, context, { embed: "@link" });
result = result["@graph"];
for (let index = result.length - 1; index >=0; index--)
{
if ((Object.keys(result[index]).length == 1) && (result[index]["@id"]))
{
result.splice(index, 1);
}
}

Object.defineProperty(expanded, "metadata", { value: result, enumerable: false });
return expanded;
}

private static parseMetadata(payload: any, result: Array<any> & { [key: string]: any }, removeFromPayload: boolean = false): any
{
if (payload instanceof Array)
{
return JsonLdMetadataProvider.parseArray(payload, result, removeFromPayload);
}

if (payload["@graph"])
{
return JsonLdMetadataProvider.parseMetadata(payload["@graph"], result, removeFromPayload);
}

return JsonLdMetadataProvider.parseResource(payload, result, removeFromPayload);
}

private static parseArray(payload: any, result: Array<any> & { [key: string]: any }, removeFromPayload: boolean = false)
{
let toBeRemoved = new Array<any>();
for (let resource of payload)
{
if (resource["@type"] && resource["@type"].every(item => item.indexOf(hydra.namespace) === 0))
{
Object.defineProperty(result, resource["@id"] || "_:bnode" + (++JsonLdMetadataProvider._id), { enumerable: false, value: resource });
result.push(resource);
if (removeFromPayload)
{
toBeRemoved.push(resource);
}
}
else
{
JsonLdMetadataProvider.parseMetadata(resource, result, removeFromPayload);
}
}

toBeRemoved.forEach(item => payload.splice(payload.indexOf(item), 1));
return result;
}

private static parseResource(resource: any, result: Array<any> & { [key: string]: any }, removeFromPayload: boolean): any
{
let targetResource;
if ((resource["@id"]) && (targetResource = result[resource["@id"]]))
{
targetResource["@id"] = resource["@id"];
}

if (!targetResource)
{
targetResource = {};
Object.defineProperty(result, "_:bnode" + (++JsonLdMetadataProvider._id), { enumerable: false, value: targetResource });
result.push(targetResource);
}

for (let property of Object.keys(resource).filter(property => property.charAt(0) !== "@"))
{
if (property.indexOf(hydra.namespace) === 0)
{
targetResource[property] = resource[property];
if (removeFromPayload)
{
delete resource[property];
}
}
}

return result;
}
}

JsonLdMetadataProvider.initialize();
24 changes: 24 additions & 0 deletions src/DataModel/JsonLd/context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"@context": {
"@vocab": "http://www.w3.org/ns/hydra/core#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"operations": "hydra:operation",
"supportedClasses": {
"@id": "hydra:supportedClass",
"@container": "@set"
},
"entryPoint": "hydra:entrypoint",
"supportedOperations": {
"@id": "hydra:supportedOperation",
"@container": "@set"
},
"supportedProperties": {
"@id": "hydra:supportedProperty",
"@container": "@set"
},
"members": {
"@id": "hydra:member",
"@container": "@set"
}
}
}
Loading