Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
siren-parser
Advanced tools
[![Build Status](https://travis-ci.org/Brightspace/node-siren-parser.svg?branch=master)](https://travis-ci.org/Brightspace/node-siren-parser) [![Coverage Status](https://coveralls.io/repos/Brightspace/node-siren-parser/badge.svg?branch=master&service=gith
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.
Install from NPM:
npm install siren-parser
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').default;
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"]}'); // parsedEntity will have printEntity()
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.
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);
// ... assuming you've got all your chai stuff set up
expect(resource).to.have.sirenAction('fancy-action');
const request = require('superagent');
sirenSuperagent.perform(request, resource.getAction('fancy-action'))
.submit({key: 'value'}) // overrides default field(s) specified in action
.parse(sirenSuperagent.parse)
.end(function(err, res) {
const resource = res.body; // parsed siren resource
expect(resource).to.have.sirenProperty('some-field');
});
// Alternatively, add the parser to the global superagent parser
request.parse['application/vnd.siren+json'] = sirenSuperagent.parse;
sirenParser(String|Object siren)
Returns an Entity object with all Siren attributes specified. Input can be a Siren object or a Siren JSON string.
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'); // true
resource.hasClass('inner'); // false
resource.hasSubEntityByRel('child'); // true
resource.hasSubEntityByType('child'); // false
resource.hasLinkByRel('crazy'); // true
resource.hasProperty('three'); // false
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'); // The 'fancy-action' Action instance
resource.getActionByName('fancy-action').title; // 'A fancy action!'
resource.getLinkByRel('self'); // The 'self' Link instance
resource.getLinkByRel('crazy'); // The same Link instance as above
resource.getLinkByRel('self').rel; // ['self', 'crazy']
resource.getLinksByRel('crazy'); // Array containing two Links
resource.getSubEntitiesByRel('child'); // Array of two entities
resource.getSubEntityByRel('child'); // Single entity
resource.getSubEntityByRel('child').getLink('self').title; // 'Child entity'
resource.getSubEntityByClass('inner'); // Entity with 'inner' class
resource.getSubEntitiesByClass('inner'); // [ All Entities with 'inner' class ]
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']) // An action instance that contains both 'self' and 'crazy' classes.
resource.getActionsByClasses(['crazy', 'cool']) // Array containing two actions, both of which contain both 'crazy' and 'cool' classes. Note that an action containing only one of those will not be included in the array.
resource.getFieldByClasses(['crazy', 'self']) // A field instance that contains both 'self' and 'crazy' classes.
resource.getFieldsByClasses(['crazy', 'cool']) // Array containing two fields, both of which contain both 'crazy' and 'cool' classes. Note that a field containing only one of those will not be included in the array.
resource.getLinkByClasses(['crazy', 'self']) // A link instance that contains both 'self' and 'crazy' classes.
resource.getLinksByClasses(['crazy', 'cool']) // Array containing two links, both of which contain both 'crazy' and 'cool' classes. Note that a link containing only one of those will not be included in the array.
resource.getLinkByRels(['thing1', 'thing2']) // A link instance that contains both 'thing1' and 'thing2' rels.
resource.getLinksByRels(['thing1', 'thing2']) // Array containing two links, both of which contain both 'thing1' and 'thing2' rels. Note that a link containing only one of those will not be included in the array.
resource.getSubEntityByClasses(['crazy', 'self']) // An entity instance that contains both 'self' and 'crazy' classes.
resource.getSubEntitiesByClasses(['crazy', 'cool']) // Array containing two entities, both of which contain both 'crazy' and 'cool' classes. Note that an entity containing only one of those will not be included in the array.
resource.getSubEntityByRels(['thing1', 'thing2']) // An entity instance that contains both 'thing1' and 'thing2' rels.
resource.getSubEntitiesByRels(['thing1', 'thing2']) // Array containing two entities, both of which contain both 'thing1' and 'thing2' rels. Note that an entity containing only one of those will not be included in the array.
Link
Attributes:
rel
(array of strings) Requiredhref
(string) Requiredclass
(array of strings)title
(string)type
(string) Must be a Siren Link typeLink.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'); // false
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'); // false
resource.getActionByName('fancy-action').hasFieldByName('max'); // true
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'); // The 'max' Field instance
resource.getActionByName('fancy-action').getFieldByName('max').title; // 'Maximum value'
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'); // false
chai
interfaceThere 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:
expect(resource).to.have.a.sirenAction.with.method('GET');
expect(resource).to.have.sirenLinks.with.classes('foo', 'bar'); // Will pass if at least 1 of resource's actions have each given class
expect(resource).to.have.sirenLinks.all.with.classes('foo', 'bar'); // Passes only if all of resource's actions have all given class
expect(resource).to.have.a.sirenEntity.with.a.sirenEntity.with.title('foo'); // Check a sub-sub-entity's title
superagent
interfaceThere 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 objectnpm test
This repo is configured to use semantic-release
. Commits prefixed with fix:
and feat:
will trigger patch and minor releases when merged to main
.
To learn how to create major releases and release from maintenance branches, refer to the semantic-release GitHub Action documentation.
This repository is configured with EditorConfig and ESLint and rules.
FAQs
[![Build Status](https://travis-ci.org/Brightspace/node-siren-parser.svg?branch=master)](https://travis-ci.org/Brightspace/node-siren-parser) [![Coverage Status](https://coveralls.io/repos/Brightspace/node-siren-parser/badge.svg?branch=master&service=gith
We found that siren-parser demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.