node-siren-parser
Parses a Siren object (or Siren JSON string) into an Entity object that is intended to be easier to work with and test, and prevent having to parse replies from Siren APIs manually. Fully implements the Siren spec, including all restrictions and requirements on entities, links, fields, etc. Kinda complements node-siren-writer, in that they're intended to sort of be opposites. Also includes a plugin for use with chai.
Installation
Install from NPM:
npm install siren-parser
Usage
There are three ways to use siren-parser
's functionality.
-
In Node.js, require
it as you would any other NPM package:
const SirenParse = require('siren-parser');
var parsedEntity = SirenParse('{"class":["foo","bar"]}');
-
An ES6 module is available as well for import:
import SirenParse from 'siren-parser';
var parsedEntity = SirenParse('{"class":["foo","bar"]}');
You can also import Action
, Entity
, and Link
by name if you need to add custom functionality to parsed entities.
import SirenParse, { Action, Entity, Link } from 'siren-parser';
Entity.prototype.printEntity = function() { console.log(this) };
var parsedEntity = SirenParse('{"class":["foo","bar"]}');
-
An ES6 module installed on the window as a global:
<script type="module" src="siren-parser/global.js"></script>
<script>
var parsedEntity = D2L.Hypermedia.Siren.Parse('{"class":["foo","bar"]}');
</script>
Note that this is a deumdify
'd browser bundle, which should prevent collisions with other modules on the page that are exposed by browserify's standalone UMD bundle.
API
const sirenParser = require('siren-parser');
const sirenParserChai = require('siren-parser/chai');
const sirenSuperagent = require('siren-parser/superagent');
const sirenJson = {
title: 'My title',
class: ['outer'],
links: [{
rel: ['self', 'crazy'],
href: 'http://example.com'
}, {
rel: ['crazy'],
href: 'http://example2.com'
}],
actions: [{
name: 'fancy-action',
href: 'http://example.com',
title: 'A fancy action!',
method: 'GET',
fields: [{
name: 'max',
title: 'Maximum value'
}]
}],
entities: [{
class: ['inner', 'smaller'],
rel: ['child'],
links: [{
rel: ['self'],
href: 'http://example.com/child',
title: 'Child entity'
}]
}],
properties: {
one: 1,
two: 2,
pi: 'is exactly three'
}
};
const resource = sirenParser(sirenJson);
expect(resource).to.have.sirenAction('fancy-action');
const request = require('superagent');
sirenSuperagent.perform(request, resource.getAction('fancy-action'))
.submit({key: 'value'})
.parse(sirenSuperagent.parse)
.end(function(err, res) {
const resource = res.body;
expect(resource).to.have.sirenProperty('some-field');
});
request.parse['application/vnd.siren+json'] = sirenSuperagent.parse;
API
sirenParser(String|Object siren)
Returns an Entity object with all Siren attributes specified. Input can be a Siren object or a Siren JSON string.
Helper functions
A set of helper functions are defined in the parser to help make working with the Siren resources easier. In general, each resource type has a set of hasXByY
and getXByY
functions, where X and Y are Siren resource types and Siren resource properties, respectively. There are some "shortcut" functions that are kept around for compatibility (e.g. Entity.hasAction(name)
), but these are less explicit, so it is recommended to use the newer, more explicit functions (i.e. Entity.hasActionByName(name)
).
These are contextually correct with respect to the Siren spec - as an example, there is no Entity.getActionsByName
(only Entity.getActionByName
, singular), as Action names must be unique on an Entity, meaning only one would ever be returned anyway.
It is also important to note that any "singular" functions (e.g. Entity.getActionByName
, as opposed to Entity.getActionsByName
) just returns the first matching resource, with no guarantees of order. This is useful in situations where you know there is only one matching resource, but do be careful with its use.
See each resource section for a full list of helper functions for that resource type.
Entity
Attributes:
rel
(array of strings) Required if Entity is a sub-entitytitle
(string)type
(string)properties
(object)class
(array of strings)actions
(array of Actions)links
(array of Links)entities
(array of Entities)
Each of these can be accessed as Entity.attribute
, e.g. if one of the input's properties
isfoo
, it would be accessed as Entity.properties.foo
.
Note that only those attributes present in the input will be copied into the Entity
, i.e. if your input has no links, Entity.links
will not be set, rather than being an empty array.
Entity.hasXByY(String|RegExp key)
Returns true if the Entity has an X with a Y of key
(or which matches RegExp key
), otherwise false.
hasActionByName(name)
hasActionByClass(class)
hasClass(class)
hasSubEntityByRel(rel)
hasSubEntityByClass(class)
hasSubEntityByType(type)
hasLinkByRel(rel)
hasLinkByClass(class)
hasLinkByType(type)
hasProperty(prop)
resource.hasActionByName('fancy-action');
resource.hasClass('inner');
resource.hasSubEntityByRel('child');
resource.hasSubEntityByType('child');
resource.hasLinkByRel('crazy');
resource.hasProperty('three');
Entity.getXByY(String|RegExp key)
Returns the resource(s) of type X with a Y value of key
(or which matches RegExp key
). If the requested X is singular, then the result is either the matching instance of X, or undefined. If the requested X is plural, then the result is either an Array of the matching instances of X, or an empty Array.
getActionByName(name)
- returns Action or undefinedgetActionByClass(class)
- returns Action or undefinedgetLinkByRel(rel)
- returns Link or undefinedgetLinkByClass(class)
- returns Link or undefinedgetLinkByType(type)
- returns Link or undefinedgetSubEntityByRel(rel)
(getSubEntity(rel)
) - returns Entity or undefinedgetSubEntityByClass(class)
- returns Entity or undefinedgetSubEntityByType(type)
- returns Entity or undefinedgetActionsByClass(class)
- returns Array of Actions (empty Array if none match)getLinksByRel(rel)
- returns Array of Links (empty Array if none match)getLinksByClass(class)
- returns Array of Links (empty Array if none match)getLinksByType(type)
- returns Array of Links (empty Array if none match)getSubEntitiesByRel(rel)
- returns Array of Entities (empty Array if none match)getSubEntitiesByClass(class)
- returns Array of Entities (empty Array if none match)getSubEntitiesByType(type)
- returns Array of Entities (empty Array if none match)
resource.getActionByName('fancy-action');
resource.getActionByName('fancy-action').title;
resource.getLinkByRel('self');
resource.getLinkByRel('crazy');
resource.getLinkByRel('self').rel;
resource.getLinksByRel('crazy');
resource.getSubEntitiesByRel('child');
resource.getSubEntityByRel('child');
resource.getSubEntityByRel('child').getLink('self').title;
resource.getSubEntityByClass('inner');
resource.getSubEntitiesByClass('inner');
Entity.getXByY'es(Array[String|RegExp key])
Returns the resource(s) of type X with matching Y values of multiple key
(or which matches RegExp key
). The requested X must contain each of the Y values. If the requested X is singular, then the result is either the matching instance of X, or undefined. If the requested X is plural, then the result is either an Array of the matching instances of X, or an empty Array.
getActionByClasses(classes)
- returns Action or undefinedgetActionsByClasses(classes)
- returns Array of Action (empty Array if none match)getFieldByClasses(classes)
- returns Field or undefinedgetFieldsByClasses(classes)
- returns Array of Field (empty Array if none match)getLinkByClasses(class)
- returns Link or undefinedgetLinksByClasses(class)
- return Array of Link (empty Array if none match)getLinkByRels(class)
- returns Link or undefinedgetLinksByRels(class)
- return Array of Link (empty Array if none match)getSubEntityByClasses(classes)
- returns Entity or undefinedgetSubEntitiesByClasses(classes)
- returns Array of Entity (empty Array if none match)getSubEntityByRels(classes)
- returns Entity or undefinedgetSubEntitiesByRels(classes)
- returns Array of Entity (empty Array if none match)
resource.getActionByClasses(['crazy', 'self'])
resource.getActionsByClasses(['crazy', 'cool'])
resource.getFieldByClasses(['crazy', 'self'])
resource.getFieldsByClasses(['crazy', 'cool'])
resource.getLinkByClasses(['crazy', 'self'])
resource.getLinksByClasses(['crazy', 'cool'])
resource.getLinkByRels(['thing1', 'thing2'])
resource.getLinksByRels(['thing1', 'thing2'])
resource.getSubEntityByClasses(['crazy', 'self'])
resource.getSubEntitiesByClasses(['crazy', 'cool'])
resource.getSubEntityByRels(['thing1', 'thing2'])
resource.getSubEntitiesByRels(['thing1', 'thing2'])
Link
Attributes:
rel
(array of strings) Requiredhref
(string) Requiredclass
(array of strings)title
(string)type
(string) Must be a Siren Link type
Link.hasClass(String class)
Links only have this one helper function. Returns true if the Link has the specified class
, otherwise false.
resource.getLink('crazy').hasClass('foo');
Action
Attributes:
name
(string) Requiredhref
(string) Requiredclass
(array of strings)method
(string)title
(string)type
(string) Must be a Siren Action typefields
(array of Fields)
Action.hasXByY(String|RegExp key)
Returns true if the Action has an X with a Y of key
(or which matches RegExp key
), otherwise false.
hasFieldByName(name)
(hasField(name)
) - returns true if any action on the entity has an action named name
hasFieldByClass(class)
hasFieldByType(type)
hasClass(class)
resource.hasClass('foo');
resource.getActionByName('fancy-action').hasFieldByName('max');
Action.getXByY(String|RegExp key)
Returns the resource(s) of type X with a Y value of key
(or which matches RegExp key
). If the requested X is singular, then the result is either the matching instance of X, or undefined. If the requested X is plural, then the result is either an Array of the matching instances of X, or an empty Array.
getFieldByName(name)
(getField(name)
) - returns Field or undefinedgetFieldByClass(class)
- returns Field or undefinedgetFieldByType(type)
- returns Field or undefinedgetFieldsByClass(class)
- returns Array of Fields (empty Array if none match)getFieldsByType(type)
- returns Array of Fields (empty Array if none match)
resource.getActionByName('fancy-action').getFieldByName('max');
resource.getActionByName('fancy-action').getFieldByName('max').title;
Field
Attributes:
name
(string) Requiredvalue
(string)class
(array of strings)type
(string) Must be a Siren Field typetitle
(string)min
(number)max
(number)
Field.hasClass(String class)
Fields only have this one helper function. Returns true if the Field has the specified class
, otherwise false.
resource.getAction('fancy-action').getField('max').hasClass('foo');
chai
interface
There are a few helper chai
methods included with this module, under ./chai
. These are intended to make testing with chai
cleaner. The chai interface adds the following methods and properties:
- Properties
- sirenAction/sirenActions - changes the subject of the assertion to the entity's Actions (or throws if it has none)
- sirenEntity/sirenEntities - changes the subject of the assertion to the entity's sub-Entities (or throws if it has none)
- sirenLink/sirenLinks - changes the subject of the assertion to the entity's Links (or throws if it has none)
- sirenProperty/sirenProperties - changes the subject of the assertion to the entity's Properties (or throws if it has none)
- sirenField/sirenFields - changes the subject of the assertion to the action's Fields (or throws if it has none)
- all - flags further operations to occur on all of the subject, rather than just any
- Methods:
- classes(cls1, cls2, ...) - asserts whether the subject has all the given classes
- href(href) - asserts whether the subject has the given href
- method(method) - asserts whether the subject has the given method
- name(name) - asserts whether the subject (Action(s)) has the given name
- rels(rel1, rel2, ...) - asserts whether the subject has all the given rels
- title(title) - asserts whether the subject has the given title
- type(type) - asserts whether the subject has the given title
- value(value) - asserts whether the subject (Field(s)) has the given value
expect(resource).to.have.a.sirenAction.with.method('GET');
expect(resource).to.have.sirenLinks.with.classes('foo', 'bar');
expect(resource).to.have.sirenLinks.all.with.classes('foo', 'bar');
expect(resource).to.have.a.sirenEntity.with.a.sirenEntity.with.title('foo');
superagent
interface
There are two helper superagent
methods included with this module, under ./superagent
.
.parse(sirenSuperagent.parse)
- To be used with superagent
's .parse()
methodsirenSuperagent.perform(request, action)
- Returns unended superagent
request object
Testing
npm test
Releasing
This repository uses the semantic-release GitHub Action to create new versions. See Triggering a Release for more information about automatically creating releases on merge.
Contributing
-
Fork the repository. Committing directly against this repository is highly discouraged.
-
Make your modifications in a branch, updating and writing new tests as necessary in the test
directory.
-
Ensure that all tests pass with npm test
-
rebase
your changes against master. Do not merge.
-
Submit a pull request to this repository. Wait for tests to run and someone to chime in.
Code Style
This repository is configured with EditorConfig and ESLint
and rules.