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

Check for title/slug field on config load. #1203

Merged
merged 7 commits into from
May 25, 2018
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
12 changes: 11 additions & 1 deletion src/actions/__tests__/config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,17 @@ describe('config', () => {

describe('validateConfig', () => {
it('should return the config if no errors', () => {
const config = fromJS({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [{}] });
const collections = [{
name: 'posts',
folder: '_posts',
fields: [{ name: 'title', label: 'title' }],
}];
const config = fromJS({
foo: 'bar',
backend: { name: 'bar' },
media_folder: 'baz',
collections,
});
expect(
validateConfig(config)
).toEqual(config);
Expand Down
41 changes: 41 additions & 0 deletions src/actions/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import yaml from "js-yaml";
import { Map, List, fromJS } from "immutable";
import { trimStart, flow, isBoolean, get } from "lodash";
import { authenticateUser } from "Actions/auth";
import { formatByExtension, supportedFormats, frontmatterFormats } from "Formats/formats";
import { selectIdentifier } from "Reducers/collections";
import { IDENTIFIER_FIELDS } from "Constants/fieldInference";
import * as publishModes from "Constants/publishModes";

export const CONFIG_REQUEST = "CONFIG_REQUEST";
Expand Down Expand Up @@ -40,6 +43,38 @@ export function applyDefaults(config) {
});
}

function validateCollection(collection) {
Copy link
Contributor

@erquhart erquhart May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a new function, relocated from the collections reducer (because it validates configuration), with the exception of the last conditional.

const {
name,
folder,
files,
format,
extension,
frontmatter_delimiter: delimiter,
fields,
} = collection.toJS();

if (!folder && !files) {
throw new Error(`Unknown collection type for collection "${name}". Collections can be either Folder based or File based.`);
}
if (format && !supportedFormats.includes(format)) {
throw new Error(`Unknown collection format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
}
if (!format && extension && !formatByExtension(extension)) {
// Cannot infer format from extension.
throw new Error(`Please set a format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
}
if (delimiter && !frontmatterFormats.includes(format)) {
// Cannot set custom delimiter without explicit and proper frontmatter format declaration
throw new Error(`Please set a proper frontmatter format for collection "${name}" to use a custom delimiter. Supported frontmatter formats are yaml-frontmatter, toml-frontmatter, and json-frontmatter.`);
}
if (folder && !selectIdentifier(collection)) {
// Verify that folder-type collections have an identifier field for slug creation.
throw new Error(`Collection "${name}" must have a field that is a valid entry identifier. Supported fields are ${IDENTIFIER_FIELDS.join(', ')}.`);
}
}


export function validateConfig(config) {
if (!config.get('backend')) {
throw new Error("Error in configuration file: A `backend` wasn't found. Check your config.yml file.");
Expand Down Expand Up @@ -70,6 +105,12 @@ export function validateConfig(config) {
if (!List.isList(collections) || collections.isEmpty() || !collections.first()) {
throw new Error("Error in configuration file: Your `collections` must be an array with at least one element. Check your config.yml file.");
}

/**
* Validate Collections
*/
config.get('collections').forEach(validateCollection);

return config;
}

Expand Down
28 changes: 10 additions & 18 deletions src/backends/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
selectEntryPath,
selectAllowNewEntries,
selectAllowDeletion,
selectFolderEntryExtension
selectFolderEntryExtension,
selectIdentifier,
} from "Reducers/collections";
import { createEntry } from "ValueObjects/Entry";
import { sanitizeSlug } from "Lib/urlHelper";
Expand Down Expand Up @@ -41,23 +42,14 @@ class LocalStorageAuthStore {
}
}

const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => {
const slugFormatter = (collection, entryData, slugConfig) => {
const template = collection.get('slug') || "{{slug}}";
const date = new Date();

const getIdentifier = (entryData) => {
const validIdentifierFields = ["title", "path"];
const identifiers = validIdentifierFields.map((field) =>
entryData.find((_, key) => key.toLowerCase().trim() === field)
);

const identifier = identifiers.find(ident => ident !== undefined);

if (identifier === undefined) {
throw new Error("Collection must have a field name that is a valid entry identifier");
}

return identifier;
};
const identifier = entryData.get(selectIdentifier(collection));
if (!identifier) {
throw new Error("Collection must have a field name that is a valid entry identifier");
}

const slug = template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => {
switch (field) {
Expand All @@ -74,7 +66,7 @@ const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => {
case "second":
return (`0${ date.getSeconds() }`).slice(-2);
case "slug":
return getIdentifier(entryData).trim();
return identifier.trim();
default:
return entryData.get(field, "").trim();
}
Expand Down Expand Up @@ -248,7 +240,7 @@ class Backend {
if (!selectAllowNewEntries(collection)) {
throw (new Error("Not allowed to create new entries in this collection"));
}
const slug = slugFormatter(collection.get("slug"), entryDraft.getIn(["entry", "data"]), config.get("slug"));
const slug = slugFormatter(collection, entryDraft.getIn(["entry", "data"]), config.get("slug"));
const path = selectEntryPath(collection, slug);
entryObj = {
path,
Expand Down
2 changes: 2 additions & 0 deletions src/constants/fieldInference.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';

/* eslint-disable */
export const IDENTIFIER_FIELDS = ['title', 'path'];

export const INFERABLE_FIELDS = {
title: {
type: 'string',
Expand Down
35 changes: 6 additions & 29 deletions src/reducers/collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { has, get, escapeRegExp } from 'lodash';
import consoleError from 'Lib/consoleError';
import { CONFIG_SUCCESS } from 'Actions/config';
import { FILES, FOLDER } from 'Constants/collectionTypes';
import { INFERABLE_FIELDS } from 'Constants/fieldInference';
import { formatByExtension, formatToExtension, supportedFormats, frontmatterFormats } from 'Formats/formats';
import { INFERABLE_FIELDS, IDENTIFIER_FIELDS } from 'Constants/fieldInference';
import { formatToExtension } from 'Formats/formats';

const collections = (state = null, action) => {
switch (action.type) {
case CONFIG_SUCCESS:
const configCollections = action.payload ? action.payload.get('collections') : List();
configCollections.forEach(validateCollection)
return configCollections
.toOrderedMap()
.map(collection => {
Expand All @@ -27,32 +26,6 @@ const collections = (state = null, action) => {
}
};

function validateCollection(configCollection) {
const {
name,
folder,
files,
format,
extension,
frontmatter_delimiter: delimiter
} = configCollection.toJS();

if (!folder && !files) {
throw new Error(`Unknown collection type for collection "${name}". Collections can be either Folder based or File based.`);
}
if (format && !supportedFormats.includes(format)) {
throw new Error(`Unknown collection format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
}
if (!format && extension && !formatByExtension(extension)) {
// Cannot infer format from extension.
throw new Error(`Please set a format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
}
if (delimiter && !frontmatterFormats.includes(format)) {
// Cannot set custom delimiter without explicit and proper frontmatter format declaration
throw new Error(`Please set a proper frontmatter format for collection "${name}" to use a custom delimiter. Supported frontmatter formats are yaml-frontmatter, toml-frontmatter, and json-frontmatter.`);
}
}

const selectors = {
[FOLDER]: {
entryExtension(collection) {
Expand Down Expand Up @@ -120,6 +93,10 @@ export const selectListMethod = collection => selectors[collection.get('type')].
export const selectAllowNewEntries = collection => selectors[collection.get('type')].allowNewEntries(collection);
export const selectAllowDeletion = collection => selectors[collection.get('type')].allowDeletion(collection);
export const selectTemplateName = (collection, slug) => selectors[collection.get('type')].templateName(collection, slug);
export const selectIdentifier = collection => {
const fieldNames = collection.get('fields').map(field => field.get('name'));
return IDENTIFIER_FIELDS.find(id => fieldNames.find(name => name.toLowerCase().trim() === id));
};
export const selectInferedField = (collection, fieldName) => {
const inferableField = INFERABLE_FIELDS[fieldName];
const fields = collection.get('fields');
Expand Down