import expect from 'expect'
import {DSL, URI, Namespace, Class, Property, Prefix, Resource, Vocab} from 'jsonld-dsl'
jsonld-dsl is a DSL for building JSON-LD powered hypermedia services. This module will serve as the view layer for a service.
Building JSON-LD services by hand often leads to inconsistencies between the context and the resources that is hard to detect.
This DSL will help you build JSON-LD services that keep their context’s consistent.
import expect from 'expect'
import {DSL, URI, Namespace, Class, Property, Prefix, Resource, Vocab} from 'jsonld-dsl'
const xhtml = Namespace(
Property('up')
)
const schema = Namespace(
Class('Thing'),
Class('Blog'),
Class('BlogPosting'),
Property('name'),
Property('url'),
Property('blogPost'),
Property('articleBody')
)
it(
'should produce a Immutable.Map() of the BlogPosting() resource',
() => {
expect(
This schema allows you to declare the classes and properties that your resources will use.
To generate a BlogPost resource is easy:
schema.BlogPosting(
URI('/entries/hydra-lite.json'),
schema.name('Hydra Lite'),
schema.url('http://eric.themoritzfamily.com/hydra-lite.html'),
schema.articleBody('This is the article body...')
).toJSON()
).toEqual(
This will produce a ResourceClass
instance which is a sub-class of
Immutable.Map()
instance that represents your resource.
{
"@id": "/entries/hydra-lite.json",
"@type": ["BlogPosting"],
"name": "Hydra Lite",
"url": "http://eric.themoritzfamily.com/hydra-lite.html",
"articleBody": "This is the article body..."
}
)
}
)
Because of the Immutable.Map#toJSON
method, rendering
the resource as JSON is as easy as calling JSON.stringify(entry)
The use of Immutable.Map()
allows us to easily and efficiently
compose resources together. Any instance of a ResourceClass
can
be composed with Resource()
it(
'Resource() allows you to compose class instances into a single resource',
() => {
expect(
Resource()
allows us render a resource that is the composition
multiple properties and classes.
For instance to compose a resource of a schema.Thing()
and
a schema.BlogPosting()
. You would do the following
Resource(
URI('/entries/hydra-lite.json'),
schema.Thing(
schema.name('Hydra Lite'),
schema.url(
'http://eric.themoritzfamily.com/hydra-lite.html'
)
),
schema.BlogPosting(
schema.articleBody('This is the article body...')
)
).toJSON()
).toEqual(
Rendered as JSON-LD, this is a really minor change:
{
"@id": "/entries/hydra-lite.json",
"@type": ["Thing", "BlogPosting"],
"name": "Hydra Lite",
"url": "http://eric.themoritzfamily.com/hydra-lite.html",
"articleBody": "This is the article body..."
}
)
}
Did you notice the difference? That’s right, it simply adds
"Thing"
to the @type
field.
)
it('should render a context correctly', () => {
You have two options when it comes to a JSON-LD you can embed the context or you can refer to it as a URL.
Let us start with how you would render a context resource:
let context = Resource(
Prefix(
This is the namespace’s prefix.
'schema',
This is the namespace’s URI
'http://schema.org/',
schema,
This is an optional context overlay
{'blogPost': {'@container': '@list'}}
),
Prefix('xhtml', 'http://www.w3.org/1999/xhtml#', xhtml)
)
expect(
context.toJSON()
).toEqual(
As JSON this context
will look like:
{
'@context': {
'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
'Blog': 'schema:Blog',
'BlogPosting': 'schema:BlogPosting',
'Thing': 'schema:Thing',
'articleBody': 'schema:articleBody',
'blogPost': {'@id': 'schema:blogPost', '@container': '@list'},
'name': 'schema:name',
'schema': 'http://schema.org/',
'up': 'xhtml:up',
'url': 'schema:url',
'xhtml': 'http://www.w3.org/1999/xhtml#',
}
}
)
expect(
Resources can then link to the context like so:
schema.BlogPosting(
{'@context': '/context.jsonld'},
URI('/entries/hydra-lite.json'),
schema.BlogPosting(
schema.name('Hydra Lite'),
schema.url(
'http://eric.themoritzfamily.com/hydra-lite.html'
),
schema.articleBody('This is the article body...')
)
).toJSON()
).toEqual(
Which will result in JSON that looks like so
{
"@context": "/context.jsonld",
"@id": "/entries/hydra-lite.json",
"@type": ["BlogPosting"],
"name": "Hydra Lite",
"url": "http://eric.themoritzfamily.com/hydra-lite.html",
"articleBody": "This is the article body..."
}
)
expect(
To Embed the context, this is really simple:
schema.BlogPosting(
context,
URI('/entries/hydra-lite.json'),
schema.BlogPosting(
schema.name('Hydra Lite'),
schema.url(
'http://eric.themoritzfamily.com/hydra-lite.html'
),
schema.articleBody('This is the article body...')
)
).toJSON()
).toEqual(
This results in JSON that looks like so:
{
'@context': {
'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
'Blog': 'schema:Blog',
'BlogPosting': 'schema:BlogPosting',
'Thing': 'schema:Thing',
'articleBody': 'schema:articleBody',
'blogPost': {'@id': 'schema:blogPost', '@container': '@list'},
'name': 'schema:name',
'schema': 'http://schema.org/',
'up': 'xhtml:up',
'url': 'schema:url',
'xhtml': 'http://www.w3.org/1999/xhtml#',
},
"@id": "/entries/hydra-lite.json",
"@type": ["BlogPosting"],
"name": "Hydra Lite",
"url": "http://eric.themoritzfamily.com/hydra-lite.html",
"articleBody": "This is the article body..."
}
)
})
it('should render a vocabulary correctly', () => {
expect(
If you need to automatically generate a vocabulary for your service, this couldn’t be any simpler:
Vocab(schema, xhtml).toJSON()
).toEqual(
{
"@graph": [
{
"@id": "Thing",
"@type": ["rdfs:Class"]
},
{
"@id": "Blog",
"@type": ["rdfs:Class"]
},
{
"@id": "BlogPosting",
"@type": ["rdfs:Class"]
},
{
"@id": "name",
"@type": ["rdfs:Property"]
},
{
"@id": "url",
"@type": ["rdfs:Property"]
},
{
"@id": "blogPost",
"@type": ["rdfs:Property"]
},
{
"@id": "articleBody",
"@type": ["rdfs:Property"]
},
{
"@id": "up",
"@type": ["rdfs:Property"]
}
]
}
)
})
it('should allow annotation of Property and Classes', () => {
Property()
and Class()
results are Resources()
like any other
so they allow you to annotate them just like any other resource
let rdfs = Namespace(
Property('comment'),
Property('range')
)
let hydra = Namespace(
Class('Link')
)
let ns = Namespace(
Class('SearchResults'),
Property(
'search',
hydra.Link(),
rdfs.comment('This is the search link'),
rdfs.range(URI('SearchResults'))
)
)
expect(
The vocabulary of this namespace will include these annotations
Resource(
Prefix('rdfs', 'http://www.w3.org/2000/01/rdf-schema#', rdfs),
Prefix('hydra', 'http://www.w3.org/ns/hydra/core#', hydra),
Prefix('ns', 'http://example.com/vocab#', ns),
Vocab(ns)
).toJSON()
).toEqual(
{
"@context": {
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"comment": "rdfs:comment",
"range": "rdfs:range",
"hydra": "http://www.w3.org/ns/hydra/core#",
"Link": "hydra:Link",
"SearchResults": "ns:SearchResults",
"search": "ns:search",
"ns": "http://example.com/vocab#"
},
"@graph": [
{
"@id": "SearchResults",
"@type": ["rdfs:Class"]
},
The search property is a Link
as well as a rdfs:Property
{
"@id": "search",
"@type": ["rdfs:Property", "Link"],
"comment": "This is the search link",
"range": { "@id": "SearchResults"}
}
]
}
)
})
import {hydra} from 'jsonld-dsl'
hydra is a part of the jsonld-dsl module. It defines all the hydra classes and properties for you
For instance, here is a hydra collection that has POST, PUT, and DELETE operations declared
it('allows you to add hydra properties and classes', () => {
expect(
Resource(
schema.Blog(),
hydra.POST(
hydra.statusCode([303, 400]),
hydra.expects('BlogPosting')
),
hydra.Collection(
hydra.member([
Resource(
URI('/entries/hydra-lite.json'),
hydra.PUT(
hydra.statusCode([204, 400]),
hydra.expects('BlogPosting')
),
hydra.DELETE(),
schema.Thing(
schema.name('Hydra Lite'),
schema.url('http://eric.themoritzfamily.com/hydra-lite.html')
),
schema.BlogPosting(
schema.articleBody('This is the article body...')
)
)
])
)
).toJSON()
).toEqual(
{
'@type': ['Blog', 'Collection'],
'operation': [
{'@type': ['Operation'], 'method': 'POST', 'expects': 'BlogPosting', 'statusCode': [303, 400]}
],
'member': [
{
'@id': '/entries/hydra-lite.json',
'operation': [
{'@type': ['Operation'], 'method': 'PUT', 'expects': 'BlogPosting', 'statusCode': [204, 400]},
{'@type': ['Operation'], 'method': 'DELETE'}
],
'@type': ['Thing', 'BlogPosting'],
'articleBody': 'This is the article body...',
'name': 'Hydra Lite',
'url': 'http://eric.themoritzfamily.com/hydra-lite.html'
}
]
}
)
})