Skip to content
This repository has been archived by the owner on Mar 11, 2020. It is now read-only.

Commit

Permalink
Merge pull request #2 from docker/v1.1
Browse files Browse the repository at this point in the history
added self referencing and function execution
  • Loading branch information
Patrick Camacho authored Sep 14, 2016
2 parents 669d740 + 25f0fc6 commit 55d4d99
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

This project adheres to [Semantic Versioning](http://semver.org/).
Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/docker/pulpo/releases) page.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,44 @@ Hydrate also allows for a second argument to be passed in that contains options:
* cast (**default true**) - whether or not to cast the value before validating
* validate (**default true**) - whether or not to validate a given value

#### Referencing Other Configuration Values
It is possible to reference other values in the configuration, which will be populated with resolved values found in the referenced key. This works for both **default** values and passed in values:

```js
const schema = new Pulpo({...schema...});

const config = schema.hydrate({
server: {
port: 8888,
hostname: 'localhost',
host: 'http://${server.hostname}:${server.port}'
}
});

console.log(config.server.host);
// http://localhost:8888
```

#### Using Functions for Configuration Values
When needed, a function can be passed in as a **config** or **default** value. These functions are passed two arguments: the config object and the string path of the property being resolved:

```js
const schema = new Pulpo({...schema...});

const config = schema.hydrate({
server: {
port: 8888,
hostname: 'localhost',
host: (config) => `http://${config['server.hostname']}:${config['server.port']}`
}
});

console.log(config.server.host);
// http://localhost:8888
```

**Note**: Configuration values are accessed through their full dot-notation strings

Properties
---
Properties are definitions for a given configuration key.
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bonito/pulpo",
"version": "1.0.0",
"version": "1.1.0",
"description": "Configuration mechanism",
"author": "Patrick Camacho <[email protected]>",
"license": "MIT",
Expand All @@ -17,7 +17,8 @@
"build": "tsc",
"deps": "next-update --tldr --keep",
"start": "npm run -s test -- --watch --coverage",
"test": "jest"
"test": "jest",
"prepublish": "npm run -s test && npm run -s build"
},
"devDependencies": {
"jest-cli": "15.1.1",
Expand Down
58 changes: 45 additions & 13 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,34 @@ export interface ParsedSchemaDefinition {
[optName: string]: Property;
}

interface HydratedConfig {
[optName: string]: any;
}

function stringLookup(str: string, config: HydratedConfig): string {
return str.replace(/(\$\{(.*)\})/gi, (match, group1, key) => {
return dotty.get(config, key);
});
}

function getter(config: HydratedConfig, value: any, path: string, validate: boolean): any {
let resolvedValue: any;

switch (typeof value) {
case 'function':
resolvedValue = value(config, path);
break;
case 'string':
resolvedValue = stringLookup(value, config);
break;
default:
resolvedValue = value;
}

if (validate) this.definition[path].validate(resolvedValue);
return resolvedValue;
};

export default class Schema {
definition: ParsedSchemaDefinition;

Expand Down Expand Up @@ -60,28 +88,32 @@ export default class Schema {
}

hydrate(rawConfig: Object, options: HydrateOptionsDefinition = {}): Object {
const hydratedConfig = Object.keys(this.definition).reduce((obj, key) => {
const flags = {
transform: !Reflect.has(options, 'transform') || options.transform,
cast: !Reflect.has(options, 'cast') || options.cast,
validate: !Reflect.has(options, 'validate') || options.validate,
}

// Loop over and hydrate the object with getters

const hydratedConfig: HydratedConfig = Object.keys(this.definition).reduce((obj, key) => {
const property = this.definition[key];

let value = property.resolve(rawConfig);

if (!Reflect.has(options, 'transform') || options.transform) {
value = property.transform(value, rawConfig);
}
if (flags.transform) value = property.transform(value, rawConfig);
if (flags.cast) value = property.cast(value);

if (!Reflect.has(options, 'cast') || options.cast) {
value = property.cast(value);
}
Object.defineProperty(obj, key, {get: getter.bind(this, obj, value, key, flags.validate) });
return obj
}, {});

if (!Reflect.has(options, 'validate') || options.validate) {
property.validate(value);
}
return Object.keys(this.definition).reduce((obj: HydratedConfig, key: string) => {
const value: any = hydratedConfig[key];

dotty.put(obj, key, value);
if (value) dotty.put(obj, key, value);
return obj;
}, {});

return hydratedConfig;
}
}

Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/self-referencing-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"foo": {
"bar": {
"description": "nested schema key",
"type": "string",
"default": "${baz}/foo"
}
},
"baz": {
"description": "top level value",
"type": "string"
}
}
19 changes: 16 additions & 3 deletions test/integration/schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Schema from '../../src/schema';
import nestedSchema = require('../fixtures/nested-schema.json');
import brokenNestedSchema = require('../fixtures/broken-nested-schema.json');
import selfReferencingSchema = require('../fixtures/self-referencing-schema.json');

describe('Resolve', () => {
it('Accepts a provided value', () => {
Expand Down Expand Up @@ -92,7 +93,7 @@ describe('Cast and Validate', () => {
number: {
description: 'description',
type: 'number',
default: 8888,
default: () => 8888,
},
string: {
description: 'description',
Expand All @@ -102,7 +103,7 @@ describe('Cast and Validate', () => {
boolean: {
description: 'description',
type: 'boolean',
default: false,
default: () => false,
},
array: {
description: 'description',
Expand All @@ -112,7 +113,7 @@ describe('Cast and Validate', () => {
object: {
description: 'description',
type: 'object',
default: {},
default: () => {},
}
});

Expand Down Expand Up @@ -172,3 +173,15 @@ describe('parsing nested schemas', () => {
});
});
});

describe('parsing self referencing strings', () => {
it('handles self references', () => {
const schema = new Schema(selfReferencingSchema);
expect(schema.hydrate({ baz: 'testing' })).toEqual({
foo: {
bar: 'testing/foo'
},
baz: 'testing'
});
});
});

0 comments on commit 55d4d99

Please sign in to comment.