diff options
| author | Oskari Timperi <oskari.timperi@iki.fi> | 2017-11-01 18:02:51 +0200 |
|---|---|---|
| committer | Oskari Timperi <oskari.timperi@iki.fi> | 2017-11-01 18:02:51 +0200 |
| commit | 2a3cf45229d8c978f492fc4db60a1399699a505b (patch) | |
| tree | e15f69f1179e47b83c4df2daf44a43b5689ef602 | |
| parent | aa23bdaf994e84e92ea99fed2d1a024bee8589e1 (diff) | |
| download | nimrec-2a3cf45229d8c978f492fc4db60a1399699a505b.tar.gz nimrec-2a3cf45229d8c978f492fc4db60a1399699a505b.zip | |
Add initial support for record sets and simple constraints
The constraints added in this commit are mandatory, prohibit and allowed.
| -rw-r--r-- | nimrec/recordset.nim | 90 | ||||
| -rw-r--r-- | tests/test.nim | 188 |
2 files changed, 278 insertions, 0 deletions
diff --git a/nimrec/recordset.nim b/nimrec/recordset.nim new file mode 100644 index 0000000..9e6d90c --- /dev/null +++ b/nimrec/recordset.nim @@ -0,0 +1,90 @@ +import parser +import record +import utils + +import sequtils +import sets +import streams +import strutils +import tables +import times + +type + RecordSet* = ref object + kind*: string + doc*: string + mandatory*: HashSet[string] + allowed*: HashSet[string] + prohibited*: HashSet[string] + + IntegrityError* = object of Exception + +proc newRecordSet*(record: Record): RecordSet = + new(result) + + init(result.mandatory) + init(result.prohibited) + init(result.allowed) + + result.kind = record["%rec"] + + if "%doc" in record: + result.doc = record["%doc"] + + for value in values(record, "%mandatory"): + for field in split(value): + incl(result.mandatory, field) + incl(result.allowed, field) + if field in result.prohibited: + raise newException(Exception, "a field cannot be mandatory and prohibited at the same time") + + for value in values(record, "%prohibit"): + for field in split(value): + if field in result.mandatory: + raise newException(Exception, "a field cannot be mandatory and prohibited at the same time") + if field in result.allowed: + raise newException(Exception, "a field cannot be allowed and prohibited at the same time") + incl(result.prohibited, field) + + for value in values(record, "%allowed"): + for field in split(value): + if field in result.prohibited: + raise newException(Exception, "a field cannot be allowed and prohibited at the same time") + incl(result.allowed, field) + +proc validate*(record: Record, recordSet: RecordSet) = + let labels = toSet(toSeq(labels(record))) + + for label in labels(record): + if len(recordSet.allowed) > 0: + if label notin recordSet.allowed: + raise newException(IntegrityError, format("not allowed: $1", label)) + + if label in recordSet.prohibited: + raise newException(IntegrityError, format("prohibited: $1", label)) + + for mandatoryLabel in recordSet.mandatory: + if mandatoryLabel notin labels: + raise newException(IntegrityError, format("mandatory: $1", + mandatoryLabel)) + +iterator recordsInSet*(stream: Stream, kind: string): Record = + var + currentSet: RecordSet = nil + + for record in records(stream): + if currentSet == nil: + if "%rec" in record and record["%rec"] == kind: + currentSet = newRecordSet(record) + continue + else: + if "%rec" in record and record["%rec"] == kind: + currentSet = newRecordSet(record) + continue + elif "%rec" in record: + currentSet = nil + continue + + validate(record, currentSet) + + yield record diff --git a/tests/test.nim b/tests/test.nim index 70a9665..9721fb0 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -222,3 +222,191 @@ Email: jane@doe.me check(len(records) == 2) check("Email" notin records[0]) check("Email" in records[1]) + +suite "recordset": + test "basics": + const data = """ +%rec: Entry +%doc: docs for Entry +""" + + var ss = newStringStream(data) + var records = toSeq(records(ss)) + var rset = newRecordSet(records[0]) + + check(rset.kind == "Entry") + check(rset.doc == "docs for Entry") + + test "descriptor skipped when iterating records": + const data = """ +%rec: Entry +%doc: docs for Entry + +Name: John + +Name: Jane + +Name: Bill +""" + + var ss = newStringStream(data) + var records = toSeq(recordsInSet(ss, "Entry")) + check(len(records) == 3) + check(records[0]["Name"] == "John") + check(records[1]["Name"] == "Jane") + check(records[2]["Name"] == "Bill") + + test "mandatory does not raise if all present": + const data = """ +%rec: Entry +%mandatory: Name Age + +Name: John +Age: 0 + +Name: Jane +Age: 0 + +Name: Bill +Age: 0 +""" + + var ss = newStringStream(data) + var records = toSeq(recordsInSet(ss, "Entry")) + check(len(records) == 3) + + test "mandatory raises if something is missing": + const data = """ +%rec: Entry +%mandatory: Name Age + +Name: John +Age: 0 + +Name: Jane + +Name: Bill +Age: 0 +""" + + var ss = newStringStream(data) + expect(IntegrityError): + discard toSeq(recordsInSet(ss, "Entry")) + + test "prohibit does not fail if fields not present": + const data = """ +%rec: Entry +%prohibit: result + +Name: John + +Name: Jane + +Name: Bill +""" + + var ss = newStringStream(data) + var records = toSeq(recordsInSet(ss, "Entry")) + check(len(records) == 3) + + test "prohibit fails if fields present": + const data = """ +%rec: Entry +%prohibit: result + +Name: John + +Name: Jane + +Name: Bill +result: 100 +""" + + var ss = newStringStream(data) + expect(IntegrityError): + discard toSeq(recordsInSet(ss, "Entry")) + + test "allowed allows allowed fields": + const data = """ +%rec: Entry +%allowed: Name Age Address + +Name: John + +Name: Jane +Age: 29 + +Name: Bill +Age: 56 +Address: 10 Foobar Way +""" + + var ss = newStringStream(data) + var records = toSeq(recordsInSet(ss, "Entry")) + check(len(records) == 3) + + test "allowed does not allow undeclared fields": + const data = """ +%rec: Entry +%allowed: Name Age Address + +Name: John + +Name: Jane +Age: 29 +Phone: 12345 + +Name: Bill +Age: 56 +Address: 10 Foobar Way +""" + + var ss = newStringStream(data) + expect(IntegrityError): + discard toSeq(recordsInSet(ss, "Entry")) + + test "mandatory and prohibit for same field fails": + const data1 = """ +%rec: Entry +%mandatory: Name +%prohibit: Name +""" + + var ss = newStringStream(data1) + var records = toSeq(records(ss)) + expect(Exception): + discard newRecordSet(records[0]) + + const data2 = """ +%rec: Entry +%prohibit: Name +%mandatory: Name +""" + + ss = newStringStream(data2) + records = toSeq(records(ss)) + expect(Exception): + discard newRecordSet(records[0]) + + test "allowed and prohibit for same field fails": + const data1 = """ +%rec: Entry +%allowed: Name +%prohibit: Name +""" + + var ss = newStringStream(data1) + var records = toSeq(records(ss)) + expect(Exception): + discard newRecordSet(records[0]) + + const data2 = """ +%rec: Entry +%prohibit: Name +%allowed: Name +""" + + ss = newStringStream(data2) + records = toSeq(records(ss)) + expect(Exception): + discard newRecordSet(records[0]) |
