Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

@financial-times/biz-ops-schema

Package Overview
Dependencies
Maintainers
15
Versions
151
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@financial-times/biz-ops-schema - npm Package Compare versions

Comparing version 0.0.3 to 0.1.0

lib/primitive-types-map.js

28

CONTRIBUTING.md

@@ -25,3 +25,3 @@ # Contributing to the biz-ops model

code: // required. Defines the code for the type
type: String // Any graphql type (see below)
type: Paragraph // Any primitive type or enum (see below)
required: true // whether or not the field is required

@@ -34,12 +34,26 @@ canIdentify: true // whether the field can be used to identify a single record

Graphql types are `String`, `Int`, `Float` and `Boolean`.
### Primitive types
In addition to these, the name of any [enum](#enums) can be used as the type of a property
Each property should have a type chosen from the following
- Word - for ids and other very short strings, generally not allowing whitespace
- Sentence - for short pieces of text
- Paragraph - for longer pieces of text
- Document - for arbitrarily long pieces of text
- Url - Urls
- Date - Dates, which should generally be input as ISO strings
- Int - Integers
- Float - Decimal numbers
- Boolean - True or False
Most of the above will be mapped to Strings in the data layer, and do not have any strict conditions attached. They are intended as hints for the underlying systems storing the data, and any systems displaying it.
In addition to the above, the name of any [enum](#enums) can be used as the type of a property
Note that yaml files are indented with two spaces
## Attributes
To add attributes to any existing type, add them in the `properties` section of the type's `.yaml` file. Property names must be camelCased.
## Relationships

@@ -79,6 +93,6 @@

## String validation rules
These are expressed as regular expressions and are used to (optionally) validate values. Define a pattern in `schema/string-patterns.yaml` by adding a property to the yaml file abserving the following rules:
- The property name must be in CONSTANT_CASE

@@ -105,5 +119,1 @@ - The value must be either

```

@@ -8,5 +8,6 @@ module.exports = Object.assign(

getGraphqlDefs: require('./methods/get-graphql-defs').method,
normalizeTypeName: name => name
normalizeTypeName: name => name,
primitiveTypesMap: require('./lib/primitive-types-map')
},
require('./lib/validate')
);
const getEnums = require('../methods/get-enums');
const getType = require('../methods/get-type');
const attributeNameRegex = /^[a-z][a-zA-Z\d]+$/;
const primitiveTypesMap = require('./primitive-types-map');
const { stripIndents } = require('common-tags');

@@ -67,3 +67,3 @@

const val = attributes[propName];
type = primitiveTypesMap[type] || type;
if (type === 'Boolean') {

@@ -70,0 +70,0 @@ if (typeof val !== 'boolean') {

@@ -1,26 +0,4 @@

const { stripIndent } = require('common-tags');
const getTypes = require('../methods/get-types').method;
const getEnums = require('../methods/get-enums').method;
// Just here to ensure 100% backwards compatibility with previous beginnings
// of graphql mutations
const customGraphql = `
input SystemInput {
serviceTier: ServiceTier
name: String
supported: Boolean
primaryURL: String
systemType: String
serviceTier: ServiceTier
serviceType: String
hostPlatform: String
personalData: Boolean
sensitiveData: Boolean
lifecycleStage: SystemLifecycle
}
type Mutation {
System(code: String, params: SystemInput): System!
}`;
const stripEmptyFirstLine = (hardCoded, ...vars) => {

@@ -76,3 +54,3 @@ hardCoded[0] = hardCoded[0].replace(/^\n+(.*)$/, ($0, $1) => $1);

const generatePropertyFields = properties => {
const defineProperties = properties => {
return properties

@@ -91,3 +69,3 @@ .map(

const PAGINATE = indentMultiline(
generatePropertyFields(
defineProperties(
Object.entries({

@@ -115,18 +93,15 @@ offset: {

const generateQuery = ({ name, type, properties, paginate }) => {
const defineQuery = ({ name, type, properties, paginate }) => {
return `
${name}(
${paginate ? PAGINATE : ''}
${indentMultiline(generatePropertyFields(properties), 4, true)}
${indentMultiline(defineProperties(properties), 4, true)}
): ${type}`;
};
module.exports.method = () => {
const typeDefinitions = getTypes({ relationshipStructure: 'graphql' }).map(
config => {
return `
const defineType = config => `
# ${config.description}
type ${config.name} {
${indentMultiline(
generatePropertyFields(Object.entries(config.properties)),
defineProperties(Object.entries(config.properties)),
2,

@@ -136,28 +111,18 @@ true

}`;
}
);
const queries = getTypes().map(config => {
return stripIndent`
${generateQuery({
name: config.name,
type: config.name,
properties: getIdentifyingFields(config)
})}
${generateQuery({
name: config.pluralName,
type: `[${config.name}]`,
properties: getFilteringFields(config),
paginate: true
})}`;
});
const defineQueries = config => [
defineQuery({
name: config.name,
type: config.name,
properties: getIdentifyingFields(config)
}),
defineQuery({
name: config.pluralName,
type: `[${config.name}]`,
properties: getFilteringFields(config),
paginate: true
})
];
const queryDefinitions = stripIndent`
type Query {
${indentMultiline(queries.join('\n\n'), 2)}
}`;
const enumDefinitions = Object.entries(getEnums({ withMeta: true })).map(
([name, { description, options }]) => {
return `
const defineEnum = ([name, { description, options }]) => `
# ${description}

@@ -167,8 +132,16 @@ enum ${name} {

}`;
}
module.exports.method = () => {
const typesFromSchema = getTypes({
primitiveTypes: 'graphql',
relationshipStructure: 'graphql'
});
return [].concat(
typesFromSchema.map(defineType),
'type Query {\n',
...typesFromSchema.map(defineQueries),
'}',
Object.entries(getEnums({ withMeta: true })).map(defineEnum)
);
return typeDefinitions.concat([queryDefinitions], enumDefinitions, [
customGraphql
]);
};

@@ -7,2 +7,3 @@ const rawData = require('../lib/raw-data');

const getStringValidator = require('../lib/get-string-validator');
const primitiveTypesMap = require('../lib/primitive-types-map');

@@ -12,2 +13,3 @@ const getType = (

{
primitiveTypes = 'biz-ops', // graphql
relationshipStructure = false // flat, rest, graphql

@@ -30,7 +32,19 @@ // groupProperties = false

Object.values(type.properties).forEach(prop => {
if (prop.pattern) {
prop.validator = getStringValidator(prop.pattern);
}
});
type.properties = Object.entries(type.properties)
.map(([name, def]) => {
if (primitiveTypes === 'graphql') {
if (def.type === 'Document') {
// documents are too big to be served by graphql
return
}
// If not a primitive type we assume it's an enum and leave it unaltered
def.type = primitiveTypesMap[def.type] || def.type;
}
if (def.pattern) {
def.validator = getStringValidator(def.pattern);
}
return [name, def]
})
.filter(entry => !!entry)
.reduce((obj, [name, def])=> Object.assign(obj, {[name]: def}), {});

@@ -37,0 +51,0 @@ if (relationshipStructure) {

{
"name": "@financial-times/biz-ops-schema",
"version": "0.0.3",
"version": "0.1.0",
"description": "Schema for biz-ops data store and api. It provides two things: - yaml files which define which types, properties and relationships are allowed - a nodejs library for extracting subsets of this information",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -9,3 +9,3 @@ const rawData = require('../../lib/raw-data');

const readYaml = require('../../lib/read-yaml');
const primitiveTypesMap = require('../../lib/primitive-types-map');
const fs = require('fs');

@@ -17,8 +17,4 @@ const path = require('path');

const validStringPatterns = Object.keys(stringPatterns);
const validPropTypes = validEnums.concat([
'String',
'Int',
'Float',
'Boolean'
]);
const validPropTypes = validEnums.concat(Object.keys(primitiveTypesMap));
fs.readdirSync(path.join(process.cwd(), 'schema/types'))

@@ -25,0 +21,0 @@ .filter(fileName => /\.yaml$/.test(fileName))

@@ -6,6 +6,14 @@ const { expect } = require('chai');

const cache = require('../../lib/cache');
const primitiveTypesMap = require('../../lib/primitive-types-map');
const explodeString = str =>
str
.split('\n')
// exclude strings which are just whitespace or empty
.filter(str => !/^[\s]*$/.test(str))
.map(str => str.trim());
describe('graphql def creation', () => {
let sandbox;
before(() => {
beforeEach(() => {
cache.clear();

@@ -15,6 +23,7 @@ sandbox = sinon.createSandbox();

after(() => {
afterEach(() => {
sandbox.restore();
cache.clear();
});
it('generates expected graphql def given schema', () => {

@@ -27,3 +36,3 @@ sandbox.stub(rawData, 'getTypes').returns([

code: {
type: 'String',
type: 'Word',
required: true,

@@ -36,3 +45,3 @@ unique: true,

name: {
type: 'String',
type: 'Word',
canIdentify: true,

@@ -49,3 +58,3 @@ description: 'The name of the cost centre'

code: {
type: 'String',
type: 'Word',
required: true,

@@ -58,3 +67,3 @@ unique: true,

name: {
type: 'String',
type: 'Word',
canIdentify: true,

@@ -101,8 +110,2 @@ description: 'The name of the group'

const explodeString = str =>
str
.split('\n')
.filter(str => !/^[\s]*$/.test(str))
.map(str => str.trim());
const generated = [].concat(...generateGraphqlDefs().map(explodeString));

@@ -189,20 +192,2 @@

Sunset
}
input SystemInput {
serviceTier: ServiceTier
name: String
supported: Boolean
primaryURL: String
systemType: String
serviceTier: ServiceTier
serviceType: String
hostPlatform: String
personalData: Boolean
sensitiveData: Boolean
lifecycleStage: SystemLifecycle
}
type Mutation {
System(code: String, params: SystemInput): System!
}`

@@ -212,2 +197,48 @@ )

});
describe('converting types', () => {
Object.entries(primitiveTypesMap).forEach(([bizopsType, graphqlType]) => {
if (bizopsType === 'Document') {
it(`Does not expose Document properties`, () => {
sandbox.stub(rawData, 'getTypes').returns([
{
name: 'Dummy',
description: 'dummy type description',
properties: {
prop: {
type: 'Document',
description: 'a description'
}
}
}
]);
sandbox.stub(rawData, 'getRelationships').returns({});
sandbox.stub(rawData, 'getEnums').returns({});
const generated = [].concat(...generateGraphqlDefs()).join('');
expect(generated).not.to.match(new RegExp(`prop: String`));
});
} else {
it(`Outputs correct type for properties using ${bizopsType}`, () => {
sandbox.stub(rawData, 'getTypes').returns([
{
name: 'Dummy',
description: 'dummy type description',
properties: {
prop: {
type: bizopsType,
description: 'a description'
}
}
}
]);
sandbox.stub(rawData, 'getRelationships').returns({});
sandbox.stub(rawData, 'getEnums').returns({});
const generated = [].concat(...generateGraphqlDefs()).join('');
expect(generated).to.match(new RegExp(`prop: ${graphqlType}`));
});
}
});
});
});

@@ -105,2 +105,28 @@ const { getType } = require('../../');

it('it maps types to graphql properties', async () => {
sandbox.stub(getRelationships, 'method');
rawData.getTypes.returns([
{
name: 'Type1',
properties: {
primitiveProp: {
type: 'Word'
},
documentProp: {
type: 'Document'
},
enumProp: {
type: 'SomeEnum'
}
}
}
]);
const type = getType('Type1', { primitiveTypes: 'graphql' });
expect(type.properties.primitiveProp).to.eql({ type: 'String' });
expect(type.properties.documentProp).to.not.exist;
expect(type.properties.enumProp).to.eql({ type: 'SomeEnum' });
});
describe('relationships', () => {

@@ -107,0 +133,0 @@ it('it includes rest api relationship definitions', async () => {

@@ -5,3 +5,3 @@ const sinon = require('sinon');

const { validateAttributes } = require('../../');
const primitiveTypesMap = require('../../lib/primitive-types-map')
describe('validateAttributes', () => {

@@ -15,41 +15,46 @@ const sandbox = sinon.createSandbox();

describe('validating strings', () => {
beforeEach(() => {
getType.method.returns({
name: 'Thing',
properties: {
prop: {
type: 'String',
validator: /^[^z]+$/ //exclude the letter z
}
}
});
});
it('accept strings', () => {
expect(() =>
validateAttributes('Thing', { prop: 'I am Tracy Beaker' })
).not.to.throw();
});
it('not accept booleans', () => {
expect(() => validateAttributes('Thing', { prop: true })).to.throw(
/Must be a string/
);
expect(() => validateAttributes('Thing', { prop: false })).to.throw(
/Must be a string/
);
});
it('not accept floats', () => {
expect(() => validateAttributes('Thing', { prop: 1.34 })).to.throw(
/Must be a string/
);
});
it('not accept integers', () => {
expect(() => validateAttributes('Thing', { prop: 134 })).to.throw(
/Must be a string/
);
});
it('apply string patterns', () => {
expect(() =>
validateAttributes('Thing', { prop: 'I am zebbedee' })
).to.throw(/Must match pattern/);
});
Object.entries(primitiveTypesMap).forEach(([bizOpsType, graphqlType]) => {
if (graphqlType === 'String') {
beforeEach(() => {
getType.method.returns({
name: 'Thing',
properties: {
prop: {
type: bizOpsType,
validator: /^[^z]+$/ //exclude the letter z
}
}
});
});
it('accept strings', () => {
expect(() =>
validateAttributes('Thing', { prop: 'I am Tracy Beaker' })
).not.to.throw();
});
it('not accept booleans', () => {
expect(() => validateAttributes('Thing', { prop: true })).to.throw(
/Must be a string/
);
expect(() => validateAttributes('Thing', { prop: false })).to.throw(
/Must be a string/
);
});
it('not accept floats', () => {
expect(() => validateAttributes('Thing', { prop: 1.34 })).to.throw(
/Must be a string/
);
});
it('not accept integers', () => {
expect(() => validateAttributes('Thing', { prop: 134 })).to.throw(
/Must be a string/
);
});
it('apply string patterns', () => {
expect(() =>
validateAttributes('Thing', { prop: 'I am zebbedee' })
).to.throw(/Must match pattern/);
});
}
})
});

@@ -56,0 +61,0 @@ describe('validating booleans', () => {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc