Skip to content

Commit 23e55e9

Browse files
committed
Splits Adapter loading from AdaptableController
- Adds dynamic prototype conformance check upon setting adapter - Throws when adapter is undefined, invalid in controller
1 parent 33fa5a7 commit 23e55e9

10 files changed

+204
-110
lines changed

spec/AdaptableController.spec.js

Lines changed: 26 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,44 @@
11

22
var AdaptableController = require("../src/Controllers/AdaptableController").AdaptableController;
33
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
4+
var FilesController = require("../src/Controllers/FilesController").FilesController;
5+
6+
var MockController = function(options) {
7+
AdaptableController.call(this, options);
8+
}
9+
MockController.prototype = Object.create(AdaptableController.prototype);
10+
MockController.prototype.constructor = AdaptableController;
411

512
describe("AdaptableController", ()=>{
6-
7-
it("should instantiate an adapter from string in object", (done) => {
8-
var adapterPath = require('path').resolve("./spec/MockAdapter");
9-
var controller = new AdaptableController({
10-
adapter: adapterPath,
11-
key: "value",
12-
foo: "bar"
13-
});
14-
15-
expect(controller.adapter instanceof Object).toBe(true);
16-
expect(controller.options.key).toBe("value");
17-
expect(controller.options.foo).toBe("bar");
18-
expect(controller.adapter.options.key).toBe("value");
19-
expect(controller.adapter.options.foo).toBe("bar");
20-
done();
21-
});
22-
23-
it("should instantiate an adapter from string", (done) => {
24-
var adapterPath = require('path').resolve("./spec/MockAdapter");
25-
var controller = new AdaptableController(adapterPath);
26-
27-
expect(controller.adapter instanceof Object).toBe(true);
28-
done();
29-
});
30-
31-
it("should instantiate an adapter from string that is module", (done) => {
32-
var adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
33-
var controller = new AdaptableController({
34-
adapter: adapterPath
35-
});
36-
37-
expect(controller.adapter instanceof FilesAdapter).toBe(true);
38-
done();
39-
});
4013

41-
it("should instantiate an adapter from function/Class", (done) => {
42-
var controller = new AdaptableController({
43-
adapter: FilesAdapter
44-
});
45-
expect(controller.adapter instanceof FilesAdapter).toBe(true);
14+
it("should use the provided adapter", (done) => {
15+
var adapter = new FilesAdapter();
16+
var controller = new FilesController(adapter);
17+
expect(controller.adapter).toBe(adapter);
4618
done();
4719
});
4820

49-
it("should instantiate the default adapter from Class", (done) => {
50-
AdaptableController.setDefaultAdapter(FilesAdapter);
51-
var controller = new AdaptableController();
52-
expect(controller.adapter instanceof FilesAdapter).toBe(true);
21+
it("should throw when creating a new mock controller", (done) => {
22+
var adapter = new FilesAdapter();
23+
expect(() => {
24+
new MockController(adapter);
25+
}).toThrow();
5326
done();
5427
});
5528

56-
it("should use the default adapter", (done) => {
57-
var adapter = new FilesAdapter();
58-
AdaptableController.setDefaultAdapter(adapter);
59-
var controller = new AdaptableController();
60-
expect(controller.adapter).toBe(adapter);
29+
it("should fail to instantiate a controller with wrong adapter", (done) => {
30+
function WrongAdapter() {};
31+
var adapter = new WrongAdapter();
32+
expect(() => {
33+
new FilesController(adapter);
34+
}).toThrow();
6135
done();
6236
});
6337

64-
it("should use the provided adapter", (done) => {
65-
var adapter = new FilesAdapter();
66-
var controller = new AdaptableController(adapter);
67-
expect(controller.adapter).toBe(adapter);
38+
it("should fail to instantiate a controller without an adapter", (done) => {
39+
expect(() => {
40+
new FilesController();
41+
}).toThrow();
6842
done();
6943
});
7044
});

spec/AdapterLoader.spec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
var AdapterLoader = require("../src/Adapters/AdapterLoader").AdapterLoader;
3+
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
4+
5+
describe("AdaptableController", ()=>{
6+
7+
it("should instantiate an adapter from string in object", (done) => {
8+
var adapterPath = require('path').resolve("./spec/MockAdapter");
9+
10+
var adapter = AdapterLoader.load({
11+
adapter: adapterPath,
12+
key: "value",
13+
foo: "bar"
14+
});
15+
16+
expect(adapter instanceof Object).toBe(true);
17+
expect(adapter.options.key).toBe("value");
18+
expect(adapter.options.foo).toBe("bar");
19+
done();
20+
});
21+
22+
it("should instantiate an adapter from string", (done) => {
23+
var adapterPath = require('path').resolve("./spec/MockAdapter");
24+
var adapter = AdapterLoader.load(adapterPath);
25+
26+
expect(adapter instanceof Object).toBe(true);
27+
expect(adapter.options).toBe(adapterPath);
28+
done();
29+
});
30+
31+
it("should instantiate an adapter from string that is module", (done) => {
32+
var adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
33+
var adapter = AdapterLoader.load({
34+
adapter: adapterPath
35+
});
36+
37+
expect(adapter instanceof FilesAdapter).toBe(true);
38+
done();
39+
});
40+
41+
it("should instantiate an adapter from function/Class", (done) => {
42+
var adapter = AdapterLoader.load({
43+
adapter: FilesAdapter
44+
});
45+
expect(adapter instanceof FilesAdapter).toBe(true);
46+
done();
47+
});
48+
49+
it("should instantiate the default adapter from Class", (done) => {
50+
var adapter = AdapterLoader.load(null, FilesAdapter);
51+
expect(adapter instanceof FilesAdapter).toBe(true);
52+
done();
53+
});
54+
55+
it("should use the default adapter", (done) => {
56+
var defaultAdapter = new FilesAdapter();
57+
var adapter = AdapterLoader.load(null, defaultAdapter);
58+
expect(adapter instanceof FilesAdapter).toBe(true);
59+
done();
60+
});
61+
62+
it("should use the provided adapter", (done) => {
63+
var originalAdapter = new FilesAdapter();
64+
var adapter = AdapterLoader.load(originalAdapter);
65+
expect(adapter).toBe(originalAdapter);
66+
done();
67+
});
68+
});

spec/FilesController.spec.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
var FilesController = require('../src/Controllers/FilesController').FilesController;
2+
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
23
var Config = require("../src/Config");
34

45
// Small additional tests to improve overall coverage
56
describe("FilesController",()=>{
67

78
it("should properly expand objects", (done) => {
89
var config = new Config(Parse.applicationId);
9-
var filesController = new FilesController();
10+
var adapter = new GridStoreAdapter();
11+
var filesController = new FilesController(adapter);
1012
var result = filesController.expandFilesInObject(config, function(){});
1113

1214
expect(result).toBeUndefined();

spec/LoggerController.spec.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,11 @@ describe('LoggerController', () => {
7676
});
7777

7878
it('should throw without an adapter', (done) => {
79-
LoggerController.setDefaultAdapter(undefined);
80-
var loggerController = new LoggerController();
79+
8180

8281
expect(() => {
83-
loggerController.getLogs();
82+
var loggerController = new LoggerController();
8483
}).toThrow();
85-
LoggerController.setDefaultAdapter(FileLoggerAdapter);
8684
done();
8785
});
8886
});

src/Adapters/AdapterLoader.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
export class AdapterLoader {
3+
static load(options, defaultAdapter) {
4+
let adapter;
5+
6+
// We have options and options have adapter key
7+
if (options) {
8+
// Pass an adapter as a module name, a function or an instance
9+
if (typeof options == "string" || typeof options == "function" || options.constructor != Object) {
10+
adapter = options;
11+
}
12+
if (options.adapter) {
13+
adapter = options.adapter;
14+
}
15+
}
16+
17+
if (!adapter) {
18+
adapter = defaultAdapter;
19+
}
20+
21+
// This is a string, require the module
22+
if (typeof adapter === "string") {
23+
adapter = require(adapter);
24+
// If it's define as a module, get the default
25+
if (adapter.default) {
26+
adapter = adapter.default;
27+
}
28+
}
29+
// From there it's either a function or an object
30+
// if it's an function, instanciate and pass the options
31+
if (typeof adapter === "function") {
32+
var Adapter = adapter;
33+
adapter = new Adapter(options);
34+
}
35+
return adapter;
36+
}
37+
}
38+
39+
export default AdapterLoader;

src/Controllers/AdaptableController.js

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ based on the parameters passed
88
99
*/
1010

11-
const DefaultAdapters = {};
12-
1311
export class AdaptableController {
1412
/**
1513
* Check whether the api call has master key or not.
@@ -22,51 +20,49 @@ export class AdaptableController {
2220
* - object: a plain javascript object (options.constructor === Object), if options.adapter is set, we'll try to load it with the same mechanics.
2321
* - function: we'll create a new instance from that function, and pass the options object
2422
*/
25-
constructor(options) {
26-
27-
let adapter;
28-
29-
// We have options and options have adapter key
30-
if (options) {
31-
// Pass an adapter as a module name, a function or an instance
32-
if (typeof options == "string" || typeof options == "function" || options.constructor != Object) {
33-
adapter = options;
34-
}
35-
if (options.adapter) {
36-
adapter = options.adapter;
37-
}
38-
}
39-
40-
if (!adapter) {
41-
adapter = this.defaultAdapter();
42-
}
43-
44-
// This is a string, require the module
45-
if (typeof adapter === "string") {
46-
adapter = require(adapter);
47-
// If it's define as a module, get the default
48-
if (adapter.default) {
49-
adapter = adapter.default;
50-
}
51-
}
52-
// From there it's either a function or an object
53-
// if it's an function, instanciate and pass the options
54-
if (typeof adapter === "function") {
55-
var Adapter = adapter;
56-
adapter = new Adapter(options);
57-
}
58-
23+
constructor(adapter, options) {
24+
this.setAdapter(adapter, options);
25+
}
26+
27+
setAdapter(adapter, options) {
28+
this.validateAdapter(adapter);
5929
this.adapter = adapter;
6030
this.options = options;
6131
}
6232

63-
defaultAdapter() {
64-
return DefaultAdapters[this.constructor.name];
33+
expectedAdapterType() {
34+
throw new Error("Subclasses should implement expectedAdapterType()");
6535
}
6636

67-
// Sets the default adapter for that Class
68-
static setDefaultAdapter(defaultAdapter) {
69-
DefaultAdapters[this.name] = defaultAdapter;
37+
validateAdapter(adapter) {
38+
39+
if (!adapter) {
40+
throw new Error(this.constructor.name+" requires an adapter");
41+
}
42+
43+
let Type = this.expectedAdapterType();
44+
// Allow skipping for testing
45+
if (!Type) {
46+
return;
47+
}
48+
49+
// Makes sure the prototype matches
50+
let mismatches = Object.getOwnPropertyNames(Type.prototype).reduce( (obj, key) => {
51+
const adapterType = typeof adapter[key];
52+
const expectedType = typeof Type.prototype[key];
53+
if (adapterType !== expectedType) {
54+
obj[key] = {
55+
expected: expectedType,
56+
actual: adapterType
57+
}
58+
}
59+
return obj;
60+
}, {});
61+
62+
if (Object.keys(mismatches).length > 0) {
63+
console.error(adapter, mismatches);
64+
throw new Error("Adapter prototype don't match expected prototype");
65+
}
7066
}
7167
}
7268

src/Controllers/FilesController.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { Parse } from 'parse/node';
33
import { randomHexString } from '../cryptoUtils';
44
import AdaptableController from './AdaptableController';
5+
import { FilesAdapter } from '../Adapters/Files/FilesAdapter';
56

67
export class FilesController extends AdaptableController {
78

@@ -29,7 +30,7 @@ export class FilesController extends AdaptableController {
2930
* with the current mount point and app id.
3031
* Object may be a single object or list of REST-format objects.
3132
*/
32-
expandFilesInObject(config, object) {
33+
expandFilesInObject(config, object) {
3334
if (object instanceof Array) {
3435
object.map((obj) => this.expandFilesInObject(config, obj));
3536
return;
@@ -52,6 +53,10 @@ export class FilesController extends AdaptableController {
5253
}
5354
}
5455
}
56+
57+
expectedAdapterType() {
58+
return FilesAdapter;
59+
}
5560
}
5661

5762
export default FilesController;

src/Controllers/LoggerController.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Parse } from 'parse/node';
22
import PromiseRouter from '../PromiseRouter';
33
import AdaptableController from './AdaptableController';
4+
import { LoggerAdapter } from '../Adapters/Logger/LoggerAdapter';
45

56
const Promise = Parse.Promise;
67
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
@@ -70,6 +71,10 @@ export class LoggerController extends AdaptableController {
7071
});
7172
return promise;
7273
}
74+
75+
expectedAdapterType() {
76+
return LoggerAdapter;
77+
}
7378
}
7479

7580
export default LoggerController;

0 commit comments

Comments
 (0)