-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a Letterbox class to handle envelopes.
- Loading branch information
Showing
3 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* @author Tom De Caluwé | ||
* @copyright 2016 Tom De Caluwé | ||
* @license Apache-2.0 | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
'use strict' | ||
|
||
/** | ||
* Construct a new letterbox accepting EDIFACT envelopes. A letterbox is a | ||
* Writable stream accepting segment objects. Enveloping is optional, so a | ||
* single message will also be accepted. Groups inside an envelope are optional | ||
* as well. When used however, every message should be in a group. | ||
* | ||
* @constructs Letterbox | ||
*/ | ||
var Letterbox = function () { | ||
var letterbox = this; | ||
|
||
this.depth = {}; | ||
this.depth.current = 0; | ||
this.depth.minimum = 0; | ||
this.depth.maximum = 2; | ||
|
||
this.next = function () { | ||
letterbox.depth.current -= 1; | ||
}; | ||
} | ||
|
||
module.exports = Letterbox; | ||
|
||
function open_envelope(name, level) { | ||
var message = ''; | ||
var depth = this.depth.current + 1; | ||
|
||
if (this.depth.current !== level) { | ||
message += 'Cannot open the ' + name + ' at the current enveloping level'; | ||
throw Error(message); | ||
} else if (depth > this.depth.maximum) { | ||
message += 'Cannot open the ' + name + ' since it has been omitted before'; | ||
throw Error(message); | ||
} else { | ||
this.depth.current = depth; | ||
this.depth.minimum = depth; | ||
} | ||
} | ||
|
||
function close_envelope(name, level) { | ||
var message = ''; | ||
var depth = this.depth.current - 1; | ||
|
||
if (depth !== level) { | ||
message += 'Cannot close the ' + name + ' at the current enveloping level'; | ||
throw Error(message); | ||
} else { | ||
this.depth.current = depth; | ||
} | ||
} | ||
|
||
/** | ||
* Accept a segment. | ||
* | ||
* @param {String} segment A segment object. | ||
*/ | ||
Letterbox.prototype.write = function (segment) { | ||
// Most of the time we are tracking segments in a message. To optimize for | ||
// this case we start by detecting if we are currently in the middle of a | ||
// message. We can do this with only one comparison. | ||
if (this.depth.current > this.depth.maximum) { | ||
this.track(segment); | ||
} else { | ||
switch (segment.name) { | ||
case 'UNB': | ||
open_envelope.call(this, 'interchange', 0); | ||
break; | ||
case 'UNG': | ||
open_envelope.call(this, 'group', 1); | ||
break; | ||
case 'UNH': | ||
if (this.depth.current < this.depth.minimum) { | ||
throw Error('Cannot omit an envelope'); | ||
} else { | ||
this.depth.maximum = this.depth.current; | ||
this.depth.current += 1; | ||
this.track(segment); | ||
} | ||
break; | ||
case 'UNE': | ||
close_envelope.call(this, 'group', 1); | ||
break; | ||
case 'UNZ': | ||
close_envelope.call(this, 'interchange', 0); | ||
break; | ||
default: | ||
throw Error('Did not expect a ' + segment + ' segment'); | ||
} | ||
} | ||
}; | ||
|
||
Letterbox.prototype.track = function () {}; | ||
|
||
module.exports = Letterbox; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
'use strict' | ||
|
||
let Letterbox = require('../letterbox.js'); | ||
|
||
describe('Letterbox', function () { | ||
let letterbox; | ||
beforeEach(function () { | ||
letterbox = new Letterbox(); | ||
spyOn(letterbox, 'track'); | ||
}); | ||
it('should accept a message without an envelope', function () { | ||
expect(function () { letterbox.write({ name: 'UNH' }); }).not.toThrow(); | ||
expect(letterbox.depth.maximum).toEqual(0); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.next(); }).not.toThrow(); | ||
expect(letterbox.depth.current).toEqual(0); | ||
}); | ||
it('cannot nest interchanges', function () { | ||
expect(function () { letterbox.write({ name: 'UNB' }); }).not.toThrow(); | ||
expect(letterbox.depth.minimum).toEqual(1); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: 'UNB' }); }).toThrow(); | ||
}); | ||
it('cannot open an interchange after a single message', function () { | ||
expect(function () { letterbox.write({ name: 'UNH' }); }).not.toThrow(); | ||
expect(letterbox.depth.maximum).toEqual(0); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.next(); }).not.toThrow(); | ||
expect(letterbox.depth.current).toEqual(0); | ||
expect(function () { letterbox.write({ name: 'UNB' }); }).toThrow(); | ||
}); | ||
it('cannot open a group without an interchange', function () { | ||
expect(function () { letterbox.write({ name: 'UNG' }); }).toThrow(); | ||
}); | ||
for (var name of ['UNB', 'UNZ', 'UNG', 'UNE']) { | ||
it('should not validate a ' + name + ' segment while tracking a message', function () { | ||
// While no valid message contains an enveloping segment in it's segment | ||
// table, this test is the equivalence of allowing such a messsage | ||
// definition. Prohibiting enveloping segments in messages should be | ||
// done by providing a correct segment table, not by algorithm design. | ||
expect(function () { letterbox.write({ name: 'UNH' }); }).not.toThrow(); | ||
expect(letterbox.depth.maximum).toEqual(0); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: name }); }).not.toThrow(); | ||
expect(letterbox.track).toHaveBeenCalled(); | ||
}); | ||
} | ||
it('should accept a message in an interchange', function () { | ||
expect(function () { letterbox.write({ name: 'UNB' }); }).not.toThrow(); | ||
expect(letterbox.depth.minimum).toEqual(1); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: 'UNH' }); }).not.toThrow(); | ||
expect(letterbox.depth.maximum).toEqual(1); | ||
expect(letterbox.depth.current).toEqual(2); | ||
expect(function () { letterbox.next(); }).not.toThrow(); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: 'UNZ' }); }).not.toThrow(); | ||
expect(letterbox.depth.current).toEqual(0); | ||
}); | ||
it('should not accept a group after a message', function () { | ||
expect(function () { letterbox.write({ name: 'UNB' }); }).not.toThrow(); | ||
expect(letterbox.depth.minimum).toEqual(1); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: 'UNH' }); }).not.toThrow(); | ||
expect(letterbox.depth.maximum).toEqual(1); | ||
expect(letterbox.depth.current).toEqual(2); | ||
expect(function () { letterbox.next(); }).not.toThrow(); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: 'UNG' }); }).toThrow(); | ||
}); | ||
it('should not accept a group without an interchange', function () { | ||
expect(function () { letterbox.write({ name: 'UNG' }); }).toThrow(); | ||
}); | ||
it('should not accept a message after a group', function () { | ||
expect(function () { letterbox.write({ name: 'UNB' }); }).not.toThrow(); | ||
expect(letterbox.depth.minimum).toEqual(1); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: 'UNG' }); }).not.toThrow(); | ||
expect(letterbox.depth.minimum).toEqual(2); | ||
expect(letterbox.depth.current).toEqual(2); | ||
expect(function () { letterbox.write({ name: 'UNE' }); }).not.toThrow(); | ||
expect(letterbox.depth.current).toEqual(1); | ||
expect(function () { letterbox.write({ name: 'UNH' }); }).toThrow(); | ||
}); | ||
}); |