aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOskari Timperi <oskari.timperi@iki.fi>2017-11-01 18:02:51 +0200
committerOskari Timperi <oskari.timperi@iki.fi>2017-11-01 18:02:51 +0200
commit2a3cf45229d8c978f492fc4db60a1399699a505b (patch)
treee15f69f1179e47b83c4df2daf44a43b5689ef602
parentaa23bdaf994e84e92ea99fed2d1a024bee8589e1 (diff)
downloadnimrec-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.nim90
-rw-r--r--tests/test.nim188
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])