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

Added types related to Library and Patent Classifications #1817

Merged
merged 5 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/curly-lizards-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'graphql-scalars': minor
---

Add new scalar types related to Library and Patent Classifications
17 changes: 17 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ import {
GraphQLAccountNumber,
GraphQLCuid,
GraphQLSemVer,
GraphQLDeweyDecimal,
GraphQLLCCSubclass,
GraphQLIPCPatent,
} from './scalars/index.js';
import { GraphQLDuration } from './scalars/iso-date/Duration.js';

Expand Down Expand Up @@ -125,6 +128,9 @@ export {
AccountNumber as AccountNumberDefinition,
Cuid as CuidDefinition,
SemVer as SemVerDefinition,
DeweyDecimal as DeweyDecimalDefinition,
LCCSubclass as LCCSubclassDefinition,
IPCPatent as IPCPatentDefinition,
} from './typeDefs.js';

export { typeDefs } from './typeDefs.js';
Expand Down Expand Up @@ -191,6 +197,8 @@ export {
GraphQLAccountNumber as AccountNumberResolver,
GraphQLCuid as CuidResolver,
GraphQLSemVer as SemVerResolver,
GraphQLDeweyDecimal as GraphQLDeweyDecimalResolver,
GraphQLIPCPatent as GraphQLIPCPatentResolver,
};

export const resolvers: Record<string, GraphQLScalarType> = {
Expand Down Expand Up @@ -255,6 +263,9 @@ export const resolvers: Record<string, GraphQLScalarType> = {
AccountNumber: GraphQLAccountNumber,
Cuid: GraphQLCuid,
SemVer: GraphQLSemVer,
DeweyDecimal: GraphQLDeweyDecimal,
LCCSubclass: GraphQLLCCSubclass,
IPCPatent: GraphQLIPCPatent,
};

export {
Expand Down Expand Up @@ -319,6 +330,9 @@ export {
AccountNumber as AccountNumberMock,
Cuid as CuidMock,
SemVer as SemVerMock,
DeweyDecimal as DeweyDecimalMock,
LCCSubclass as LCCSubclassMock,
IPCPatent as IPCPatentMock,
} from './mocks.js';

export { mocks };
Expand Down Expand Up @@ -387,4 +401,7 @@ export {
GraphQLAccountNumber,
GraphQLCuid,
GraphQLSemVer,
GraphQLDeweyDecimal,
GraphQLLCCSubclass,
GraphQLIPCPatent,
};
3 changes: 3 additions & 0 deletions src/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export const RoutingNumber = () => '111000025';
export const AccountNumber = () => '000000012345';
export const Cuid = () => 'cjld2cyuq0000t3rmniod1foy';
export const SemVer = () => '1.0.0-alpha.1';
export const DeweyDecimal = () => '435.4357';
export const LCCSubclass = () => 'KBM';
export const IPCPatent = () => 'G06F 12/803';

export {
DateMock as Date,
Expand Down
3 changes: 3 additions & 0 deletions src/scalars/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ export { GraphQLRoutingNumber } from './RoutingNumber.js';
export { GraphQLAccountNumber } from './AccountNumber.js';
export { GraphQLCuid } from './Cuid.js';
export { GraphQLSemVer } from './SemVer.js';
export { GraphQLDeweyDecimal } from './library/DeweyDecimal.js';
export { GraphQLLCCSubclass } from './library/LCCSubclass.js';
export { GraphQLIPCPatent } from './patent/IPCPatent.js';
48 changes: 48 additions & 0 deletions src/scalars/library/DeweyDecimal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { GraphQLScalarTypeConfig, ASTNode, Kind, GraphQLScalarType } from 'graphql';
import { createGraphQLError } from '../../error.js';

const DEWEY_DECIMAL_REGEX = /^[0-9]{1,3}(?:\.[0-9]+)?$/;

const validate = (value: any, ast?: ASTNode) => {
if (typeof value !== 'string') {
throw createGraphQLError(`Value is not string: ${value}`, { nodes: ast });
}

if (!DEWEY_DECIMAL_REGEX.test(value)) {
throw createGraphQLError(`Value is not a valid Dewey Decimal Number: ${value}`, { nodes: ast });
}
return value;
};

const specifiedByURL = 'https://www.oclc.org/content/dam/oclc/dewey/resources/summaries/deweysummaries.pdf';

export const GraphQLDeweyDecimalConfig = {
name: 'DeweyDecimal',

description: `A field whose value conforms to the standard DeweyDecimal format as specified by the OCLC https://www.oclc.org/content/dam/oclc/dewey/resources/summaries/deweysummaries.pdf`,

serialize: validate,

parseValue: validate,

parseLiteral(ast) {
if (ast.kind !== Kind.STRING) {
throw createGraphQLError(`Can only validate strings as DeweyDecimal but got a: ${ast.kind}`, { nodes: ast });
}

return validate(ast.value, ast);
},

specifiedByURL,
specifiedByUrl: specifiedByURL,
extensions: {
codegenScalarType: 'string',
jsonSchema: {
title: 'DeweyDecimal',
type: 'string',
pattern: DEWEY_DECIMAL_REGEX.source,
},
},
} as GraphQLScalarTypeConfig<string, string>;

export const GraphQLDeweyDecimal: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLDeweyDecimalConfig);
52 changes: 52 additions & 0 deletions src/scalars/library/LCCSubclass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { GraphQLScalarType, GraphQLScalarTypeConfig, ASTNode, Kind } from 'graphql';
import { createGraphQLError } from '../../error.js';

//Regex for the various letter subclasses of the Library of Congress Classification
//As defined in the pdfs available by clicking on the letters at this link
//https://www.loc.gov/catdir/cpso/lcco/
const LCC_SUBCLASS_PREFIX =
/^((AC|AE|AG|AI|AM|AN|AP|AS|AY|AZ)|[B][CDFHJLMPQRSTVX]{0,1}|[C][BCDEJNRST]{0,1}|[D][AWBCDEFGH]{0,1}|[E]|[F]|[G][ABCEFNRTV]{0,1}|[H][ABCDEFGJMNQSTVX]{0,1}|[J][ACFJKLNQSVXZ]{0,1}|(K|KB[M,P,R,U]|KD[C,E,G,K,Z]{0,1}|KE[ABMNOPQSYZ]{0,1}|KF[ACDFGHIKLMNOPRSTUVWXZ]{0,1}|KG[ABCDEFGHJKLMNPQRSTUVWXYZ]{0,1}|KH[ACDFHKLMNPQSUW]{0,1}|KJ[ACEGHJKMNPRSTVW]{0,1}|KK[ABEFGHIJKLMNPQRSTVWXYZ]{0,1}|KL[ABDEFHMNPQRSTVW]{0,1}|KM[CEFGHJKLMNPQSTUVXY]{0,1}|KN[CEFGHKLMNPQRSTUVWXY]{0,1}|KP[ACEFGHJKLMPSTVW]{0,1}|KQ[CEGHJKMPTVWX]{0,1}|KR[BCEGKLMNPRSUVWXY]{0,1}|KS[ACEGHKLNPRSTUVWXYZ]{0,1}|KT[ACDEFGHJKLNQRTUVWXYZ]{0,1}|KU[ABCDEFGHNQ]{0,1}|KV[BCEHLMNPQRSUW]{0,1}|KW[ACEGHLPQRTWX]{0,1}|KZ[AD]{0,1})|[L][ABCDEFGHJT]{0,1}|[M][LT]{0,1}|[N][ABCDEKX]{0,1}|[P][ABCDEFGHJKLMNQRSTZ]{0,1}|[Q][ABCDEHKLMPR]{0,1}|[R][ABCDEFGJKLMSTVXZ]{0,1}|[S][BDFHK]{0,1}|[T][ACDEFGHJKLNPRSTX]{0,1}|[U][ABCDEFGH]{0,1}|[V][ABCDEFGKM]{0,1}|[Z][A]{0,1})$/;

const validate = (value: any, ast?: ASTNode) => {
if (typeof value !== 'string') {
throw createGraphQLError(`Value is not string: ${value}`, { nodes: ast });
}

if (!LCC_SUBCLASS_PREFIX.test(value)) {
throw createGraphQLError(`Value is not a valid LCC Subclass: ${value}`, { nodes: ast });
}
return value;
};

const specifiedByURL = 'https://www.loc.gov/catdir/cpso/lcco/';

export const GraphQLLCCSubclassConfig = {
name: 'LCCSubclass',

description: `A field whose value conforms to the Library of Congress Subclass Format ttps://www.loc.gov/catdir/cpso/lcco/`,

serialize: validate,

parseValue: validate,

parseLiteral(ast) {
if (ast.kind !== Kind.STRING) {
throw createGraphQLError(`Can only validate strings as LCC Subclasses but got a: ${ast.kind}`, { nodes: ast });
}

return validate(ast.value, ast);
},

specifiedByURL,
specifiedByUrl: specifiedByURL,
extensions: {
codegenScalarType: 'string',
jsonSchema: {
title: 'DeweyDecimal',
type: 'string',
pattern: LCC_SUBCLASS_PREFIX.source,
},
},
} as GraphQLScalarTypeConfig<string, string>;

export const GraphQLLCCSubclass: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLLCCSubclassConfig);
57 changes: 57 additions & 0 deletions src/scalars/patent/IPCPatent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { GraphQLScalarTypeConfig, ASTNode, Kind, GraphQLScalarType } from 'graphql';
import { createGraphQLError } from '../../error.js';

/* 1. [A-H] represents the Section Level of the Classification
2. \d{2} represents the class level
3. [A-Z] represents the subclass level
4. \/ separates the subclass from the subgroup
5. \d{2,4} represents the subgroup level
(Only four levels of subgroup, as far as I know)
*/
const IPC_PATENT_REGEX = /^[A-H]\d{2}[A-Z] \d{1,2}\/\d{2,4}$/;

const validate = (value: any, ast?: ASTNode) => {
if (typeof value !== 'string') {
throw createGraphQLError(`Value is not string: ${value}`, { nodes: ast });
}

if (!IPC_PATENT_REGEX.test(value)) {
throw createGraphQLError(`Value is not a valid IPC Class Symbol: ${value}`, { nodes: ast });
}
return value;
};

const specifiedByURL = 'https://www.wipo.int/classifications/ipc/en/';

export const GraphQLIPCPatentConfig = {
name: 'IPCPatentClassification',

description: `A field whose value is an IPC Class Symbol within the International Patent Classification System: https://www.wipo.int/classifications/ipc/en/`,

serialize: validate,

parseValue: validate,

parseLiteral(ast) {
if (ast.kind !== Kind.STRING) {
throw createGraphQLError(`Can only validate strings as an IPC Class Symbol but got a: ${ast.kind}`, {
nodes: ast,
});
}

return validate(ast.value, ast);
},

specifiedByURL,
specifiedByUrl: specifiedByURL,
extensions: {
codegenScalarType: 'string',
jsonSchema: {
title: 'DeweyDecimal',
type: 'string',
pattern: IPC_PATENT_REGEX.source,
},
},
} as GraphQLScalarTypeConfig<string, string>;

export const GraphQLIPCPatent: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLIPCPatentConfig);
8 changes: 8 additions & 0 deletions src/typeDefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export const DID = 'scalar DID';
export const CountryCode = 'scalar CountryCode';
export const Locale = 'scalar Locale';

export const DeweyDecimal = 'scalar DeweyDecimal';
export const LCCSubclass = 'scalar LCCSubclass';

export const IPCPatent = 'scalar IPCPatent';

export const typeDefs = [
Date,
Time,
Expand Down Expand Up @@ -124,4 +129,7 @@ export const typeDefs = [
AccountNumber,
Cuid,
SemVer,
DeweyDecimal,
LCCSubclass,
IPCPatent,
];
98 changes: 98 additions & 0 deletions tests/DeweyDecimal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* global describe, test, expect */
import { Kind } from 'graphql/language';
import { GraphQLDeweyDecimal } from '../src/scalars/library/DeweyDecimal.js';

describe('DID', () => {
describe('valid - Dewey Decimal', () => {
test('serialize', () => {
expect(GraphQLDeweyDecimal.serialize('1')).toBe('1');
expect(GraphQLDeweyDecimal.serialize('1.2345')).toBe('1.2345');
expect(GraphQLDeweyDecimal.serialize('01')).toBe('01');
expect(GraphQLDeweyDecimal.serialize('01.2345')).toBe('01.2345');
expect(GraphQLDeweyDecimal.serialize('001')).toBe('001');
expect(GraphQLDeweyDecimal.serialize('001.2345')).toBe('001.2345');
expect(GraphQLDeweyDecimal.serialize('10')).toBe('10');
expect(GraphQLDeweyDecimal.serialize('10.2345')).toBe('10.2345');
expect(GraphQLDeweyDecimal.serialize('010')).toBe('010');
expect(GraphQLDeweyDecimal.serialize('010.2345')).toBe('010.2345');
expect(GraphQLDeweyDecimal.serialize('100')).toBe('100');
expect(GraphQLDeweyDecimal.serialize('100.2345')).toBe('100.2345');
});

test('parseValue', () => {
expect(GraphQLDeweyDecimal.parseValue('1')).toBe('1');
expect(GraphQLDeweyDecimal.parseValue('1.2345')).toBe('1.2345');
expect(GraphQLDeweyDecimal.parseValue('01')).toBe('01');
expect(GraphQLDeweyDecimal.parseValue('01.2345')).toBe('01.2345');
expect(GraphQLDeweyDecimal.parseValue('001')).toBe('001');
expect(GraphQLDeweyDecimal.parseValue('001.2345')).toBe('001.2345');
expect(GraphQLDeweyDecimal.parseValue('10')).toBe('10');
expect(GraphQLDeweyDecimal.parseValue('10.2345')).toBe('10.2345');
expect(GraphQLDeweyDecimal.parseValue('010')).toBe('010');
expect(GraphQLDeweyDecimal.parseValue('010.2345')).toBe('010.2345');
expect(GraphQLDeweyDecimal.parseValue('100')).toBe('100');
expect(GraphQLDeweyDecimal.parseValue('100.2345')).toBe('100.2345');
});

test('parseLiteral', () => {
expect(GraphQLDeweyDecimal.parseLiteral({ value: '1', kind: Kind.STRING }, {})).toBe('1');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '1.2345', kind: Kind.STRING }, {})).toBe('1.2345');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '01', kind: Kind.STRING }, {})).toBe('01');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '01.2345', kind: Kind.STRING }, {})).toBe('01.2345');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '001', kind: Kind.STRING }, {})).toBe('001');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '001.2345', kind: Kind.STRING }, {})).toBe('001.2345');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '10', kind: Kind.STRING }, {})).toBe('10');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '10.2345', kind: Kind.STRING }, {})).toBe('10.2345');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '010', kind: Kind.STRING }, {})).toBe('010');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '010.2345', kind: Kind.STRING }, {})).toBe('010.2345');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '100', kind: Kind.STRING }, {})).toBe('100');
expect(GraphQLDeweyDecimal.parseLiteral({ value: '100.2345', kind: Kind.STRING }, {})).toBe('100.2345');
});
});

describe('invalid', () => {
describe('not a Dewey Decimal', () => {
expect(() => GraphQLDeweyDecimal.serialize('invaliddid')).toThrow(/Value is not a valid Dewey Decimal Number/);
});

test(`parseValue invaliddidexample`, () => {
expect(() => GraphQLDeweyDecimal.parseValue('invaliddidexample')).toThrow(
/Value is not a valid Dewey Decimal Number/
);
});

test(`parseLiteral invaliddidexample`, () => {
expect(() => GraphQLDeweyDecimal.parseLiteral({ value: 'invaliddidexample', kind: Kind.STRING }, {})).toThrow(
/Value is not a valid Dewey Decimal Number/
);
});
});

describe('not a string', () => {
test('serialize', () => {
expect(() => GraphQLDeweyDecimal.serialize(123)).toThrow();
});

test('parseValue', () => {
expect(() => GraphQLDeweyDecimal.parseValue(123)).toThrow();
});

test('parseLiteral', () => {
expect(() => GraphQLDeweyDecimal.parseLiteral({ value: '123', kind: Kind.INT }, {})).toThrow();
});
});

describe('not a empty string', () => {
test('serialize', () => {
expect(() => GraphQLDeweyDecimal.serialize('')).toThrow();
});

test('parseValue', () => {
expect(() => GraphQLDeweyDecimal.parseValue('')).toThrow();
});

test('parseLiteral', () => {
expect(() => GraphQLDeweyDecimal.parseLiteral({ value: '', kind: Kind.STRING }, {})).toThrow();
});
});
});
Loading