ovsdb: Add support for "enum" constraints.
authorBen Pfaff <blp@nicira.com>
Thu, 25 Feb 2010 22:59:33 +0000 (14:59 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 25 Feb 2010 22:59:41 +0000 (14:59 -0800)
Some of the uses for the formerly supported regular expression constraints
were simply to limit values to those in a set of allowed values.
This commit adds support for that kind of simple enumeration constraint.

12 files changed:
lib/ovsdb-data.c
lib/ovsdb-data.h
lib/ovsdb-parser.h
lib/ovsdb-types.c
lib/ovsdb-types.h
ovsdb/SPECS
ovsdb/mutation.c
ovsdb/ovsdb-idlc.in
tests/ovs-vsctl.at
tests/ovsdb-data.at
tests/ovsdb-types.at
vswitchd/vswitch.ovsschema

index 9d8eab8..42fdf1e 100644 (file)
@@ -590,6 +590,25 @@ struct ovsdb_error *
 ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
                              const struct ovsdb_base_type *base)
 {
+    if (base->enum_
+        && ovsdb_datum_find_key(base->enum_, atom, base->type) == UINT_MAX) {
+        struct ovsdb_error *error;
+        struct ds actual = DS_EMPTY_INITIALIZER;
+        struct ds valid = DS_EMPTY_INITIALIZER;
+
+        ovsdb_atom_to_string(atom, base->type, &actual);
+        ovsdb_datum_to_string(base->enum_,
+                              ovsdb_base_type_get_enum_type(base->type),
+                              &valid);
+        error = ovsdb_error("constraint violation",
+                            "%s is not one of the allowed values (%s)",
+                            ds_cstr(&actual), ds_cstr(&valid));
+        ds_destroy(&actual);
+        ds_destroy(&valid);
+
+        return error;
+    }
+
     switch (base->type) {
     case OVSDB_TYPE_VOID:
         NOT_REACHED();
@@ -777,7 +796,7 @@ ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b)
 }
 
 struct ovsdb_datum_sort_cbdata {
-    const struct ovsdb_type *type;
+    enum ovsdb_atomic_type key_type;
     struct ovsdb_datum *datum;
 };
 
@@ -788,7 +807,7 @@ ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_)
 
     return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
                                    &cbdata->datum->keys[b],
-                                   cbdata->type->key.type);
+                                   cbdata->key_type);
 }
 
 static void
@@ -797,13 +816,13 @@ ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
     struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
 
     ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
-    if (cbdata->type->value.type != OVSDB_TYPE_VOID) {
+    if (cbdata->datum->values) {
         ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
     }
 }
 
 struct ovsdb_error *
-ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+ovsdb_datum_sort(struct ovsdb_datum *datum, enum ovsdb_atomic_type key_type)
 {
     if (datum->n < 2) {
         return NULL;
@@ -811,15 +830,15 @@ ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
         struct ovsdb_datum_sort_cbdata cbdata;
         size_t i;
 
-        cbdata.type = type;
+        cbdata.key_type = key_type;
         cbdata.datum = datum;
         sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb,
              &cbdata);
 
         for (i = 0; i < datum->n - 1; i++) {
             if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
-                                  type->key.type)) {
-                if (ovsdb_type_is_map(type)) {
+                                  key_type)) {
+                if (datum->values) {
                     return ovsdb_error(NULL, "map contains duplicate key");
                 } else {
                     return ovsdb_error(NULL, "set contains duplicate");
@@ -831,6 +850,16 @@ ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
     }
 }
 
+void
+ovsdb_datum_sort_assert(struct ovsdb_datum *datum,
+                        enum ovsdb_atomic_type key_type)
+{
+    struct ovsdb_error *error = ovsdb_datum_sort(datum, key_type);
+    if (error) {
+        NOT_REACHED();
+    }
+}
+
 /* Checks that each of the atoms in 'datum' conforms to the constraints
  * specified by its 'type'.  Returns an error if a constraint is violated,
  * otherwise a null pointer.
@@ -931,7 +960,7 @@ ovsdb_datum_from_json(struct ovsdb_datum *datum,
             datum->n++;
         }
 
-        error = ovsdb_datum_sort(datum, type);
+        error = ovsdb_datum_sort(datum, type->key.type);
         if (error) {
             goto error;
         }
@@ -1128,7 +1157,7 @@ ovsdb_datum_from_string(struct ovsdb_datum *datum,
         goto error;
     }
 
-    dberror = ovsdb_datum_sort(datum, type);
+    dberror = ovsdb_datum_sort(datum, type->key.type);
     if (dberror) {
         ovsdb_error_destroy(dberror);
         if (ovsdb_type_is_map(type)) {
@@ -1424,7 +1453,7 @@ ovsdb_datum_union(struct ovsdb_datum *a, const struct ovsdb_datum *b,
     if (n != a->n) {
         struct ovsdb_error *error;
         a->n = n;
-        error = ovsdb_datum_sort(a, type);
+        error = ovsdb_datum_sort(a, type->key.type);
         assert(!error);
     }
 }
@@ -1452,8 +1481,7 @@ ovsdb_datum_subtract(struct ovsdb_datum *a, const struct ovsdb_type *a_type,
         }
     }
     if (changed) {
-        struct ovsdb_error *error = ovsdb_datum_sort(a, a_type);
-        assert(!error);
+        ovsdb_datum_sort_assert(a, a_type->key.type);
     }
 }
 \f
index 063536b..a5c49f9 100644 (file)
@@ -122,7 +122,12 @@ void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *);
 
 /* Checking and maintaining invariants. */
 struct ovsdb_error *ovsdb_datum_sort(struct ovsdb_datum *,
-                                     const struct ovsdb_type *);
+                                     enum ovsdb_atomic_type key_type)
+    WARN_UNUSED_RESULT;
+
+void ovsdb_datum_sort_assert(struct ovsdb_datum *,
+                             enum ovsdb_atomic_type key_type);
+
 struct ovsdb_error *ovsdb_datum_check_constraints(
     const struct ovsdb_datum *, const struct ovsdb_type *)
     WARN_UNUSED_RESULT;
index a27563a..d6270bb 100644 (file)
@@ -49,6 +49,8 @@ enum ovsdb_parser_types {
     OP_INTEGER = 1 << JSON_INTEGER,       /* 123. */
     OP_NONINTEGER = 1 << JSON_REAL,       /* 123.456. */
     OP_STRING = 1 << JSON_STRING,         /* "..." */
+    OP_ANY = (OP_NULL | OP_FALSE | OP_TRUE | OP_OBJECT | OP_ARRAY
+              | OP_INTEGER | OP_NONINTEGER | OP_STRING),
 
     OP_BOOLEAN = OP_FALSE | OP_TRUE,
     OP_NUMBER = OP_INTEGER | OP_NONINTEGER,
index 5da7145..df18ee5 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "dynamic-string.h"
 #include "json.h"
+#include "ovsdb-data.h"
 #include "ovsdb-error.h"
 #include "ovsdb-parser.h"
 
@@ -115,6 +116,7 @@ void
 ovsdb_base_type_init(struct ovsdb_base_type *base, enum ovsdb_atomic_type type)
 {
     base->type = type;
+    base->enum_ = NULL;
 
     switch (base->type) {
     case OVSDB_TYPE_VOID:
@@ -151,12 +153,37 @@ ovsdb_base_type_init(struct ovsdb_base_type *base, enum ovsdb_atomic_type type)
     }
 }
 
+/* Returns the type of the 'enum_' member for an ovsdb_base_type whose 'type'
+ * is 'atomic_type'. */
+const struct ovsdb_type *
+ovsdb_base_type_get_enum_type(enum ovsdb_atomic_type atomic_type)
+{
+    static struct ovsdb_type *types[OVSDB_N_TYPES];
+
+    if (!types[atomic_type]) {
+        struct ovsdb_type *type;
+
+        types[atomic_type] = type = xmalloc(sizeof *type);
+        ovsdb_base_type_init(&type->key, atomic_type);
+        ovsdb_base_type_init(&type->value, OVSDB_TYPE_VOID);
+        type->n_min = 1;
+        type->n_max = UINT_MAX;
+    }
+    return types[atomic_type];
+}
+
 void
 ovsdb_base_type_clone(struct ovsdb_base_type *dst,
                       const struct ovsdb_base_type *src)
 {
     *dst = *src;
 
+    if (src->enum_) {
+        dst->enum_ = xmalloc(sizeof *dst->enum_);
+        ovsdb_datum_clone(dst->enum_, src->enum_,
+                          ovsdb_base_type_get_enum_type(dst->type));
+    }
+
     switch (dst->type) {
     case OVSDB_TYPE_VOID:
     case OVSDB_TYPE_INTEGER:
@@ -183,6 +210,12 @@ void
 ovsdb_base_type_destroy(struct ovsdb_base_type *base)
 {
     if (base) {
+        if (base->enum_) {
+            ovsdb_datum_destroy(base->enum_,
+                                ovsdb_base_type_get_enum_type(base->type));
+            free(base->enum_);
+        }
+
         switch (base->type) {
         case OVSDB_TYPE_VOID:
         case OVSDB_TYPE_INTEGER:
@@ -237,6 +270,10 @@ ovsdb_base_type_is_valid(const struct ovsdb_base_type *base)
 bool
 ovsdb_base_type_has_constraints(const struct ovsdb_base_type *base)
 {
+    if (base->enum_) {
+        return true;
+    }
+
     switch (base->type) {
     case OVSDB_TYPE_VOID:
         NOT_REACHED();
@@ -298,7 +335,7 @@ ovsdb_base_type_from_json(struct ovsdb_base_type *base,
 {
     struct ovsdb_parser parser;
     struct ovsdb_error *error;
-    const struct json *type;
+    const struct json *type, *enum_;
 
     if (json->type == JSON_STRING) {
         error = ovsdb_atomic_type_from_json(&base->type, json);
@@ -322,7 +359,18 @@ ovsdb_base_type_from_json(struct ovsdb_base_type *base,
     }
 
     ovsdb_base_type_init(base, base->type);
-    if (base->type == OVSDB_TYPE_INTEGER) {
+
+    enum_ = ovsdb_parser_member(&parser, "enum", OP_ANY | OP_OPTIONAL);
+    if (enum_) {
+        base->enum_ = xmalloc(sizeof *base->enum_);
+        error = ovsdb_datum_from_json(
+            base->enum_, ovsdb_base_type_get_enum_type(base->type),
+            enum_, NULL);
+        if (error) {
+            free(base->enum_);
+            base->enum_ = NULL;
+        }
+    } else if (base->type == OVSDB_TYPE_INTEGER) {
         const struct json *min, *max;
 
         min = ovsdb_parser_member(&parser, "minInteger",
@@ -395,6 +443,14 @@ ovsdb_base_type_to_json(const struct ovsdb_base_type *base)
     json = json_object_create();
     json_object_put_string(json, "type",
                            ovsdb_atomic_type_to_string(base->type));
+
+    if (base->enum_) {
+        const struct ovsdb_type *type;
+
+        type = ovsdb_base_type_get_enum_type(base->type);
+        json_object_put(json, "enum", ovsdb_datum_to_json(base->enum_, type));
+    }
+
     switch (base->type) {
     case OVSDB_TYPE_VOID:
         NOT_REACHED();
@@ -528,7 +584,7 @@ ovsdb_type_to_english(const struct ovsdb_type *type)
 struct ovsdb_error *
 ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
 {
-    type->value.type = OVSDB_TYPE_VOID;
+    ovsdb_base_type_init(&type->value, OVSDB_TYPE_VOID);
     type->n_min = 1;
     type->n_max = 1;
 
index 0ef596a..6f1727e 100644 (file)
@@ -46,6 +46,11 @@ struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
 
 struct ovsdb_base_type {
     enum ovsdb_atomic_type type;
+
+    /* If nonnull, a datum with keys of type 'type' that expresses all the
+     * valid values for this base_type. */
+    struct ovsdb_datum *enum_;
+
     union {
         struct ovsdb_integer_constraints {
             int64_t min;        /* minInteger or INT64_MIN. */
@@ -90,6 +95,7 @@ void ovsdb_base_type_destroy(struct ovsdb_base_type *);
 bool ovsdb_base_type_is_valid(const struct ovsdb_base_type *);
 bool ovsdb_base_type_has_constraints(const struct ovsdb_base_type *);
 void ovsdb_base_type_clear_constraints(struct ovsdb_base_type *);
+const struct ovsdb_type *ovsdb_base_type_get_enum_type(enum ovsdb_atomic_type);
 
 struct ovsdb_error *ovsdb_base_type_from_json(struct ovsdb_base_type *,
                                               const struct json *)
index adc6dd2..e5cc21b 100644 (file)
@@ -173,6 +173,7 @@ is represented by <database-schema>, as described below.
     <atomic-type> or a JSON object with the following members:
 
         "type": <atomic-type>              required
+        "enum": <value>                    optional
         "minInteger": <integer>            optional, integers only
         "maxInteger": <integer>            optional, integers only
         "minReal": <real>                  optional, reals only
@@ -184,6 +185,13 @@ is represented by <database-schema>, as described below.
     An <atomic-type> by itself is equivalent to a JSON object with a
     single member "type" whose value is the <atomic-type>.
 
+    "enum" may be specified as a <value> whose type is a set of one
+    or more values specified for the member "type".  If "enum" is
+    specified, then the valid values of the <base-type> are limited to
+    those in the <value>.
+
+    "enum" is mutually exclusive with the following constraints.
+
     If "type" is "integer", then "minInteger" or "maxInteger" or both
     may also be specified, restricting the valid integer range.  If
     both are specified, then the maxInteger must be greater than or
index 72d7d0c..9f09d59 100644 (file)
@@ -310,7 +310,7 @@ mutate_scalar(const struct ovsdb_type *dst_type, struct ovsdb_datum *dst,
         }
     }
 
-    error = ovsdb_datum_sort(dst, dst_type);
+    error = ovsdb_datum_sort(dst, dst_type->key.type);
     if (error) {
         ovsdb_error_destroy(error);
         return ovsdb_error("constraint violation",
index c314d61..2426e2d 100755 (executable)
@@ -18,7 +18,7 @@ class Error(Exception):
 def getMember(json, name, validTypes, description, default=None):
     if name in json:
         member = json[name]
-        if type(member) not in validTypes:
+        if len(validTypes) and type(member) not in validTypes:
             raise Error("%s: type mismatch for '%s' member"
                         % (description, name))
         return member
@@ -108,13 +108,89 @@ def escapeCString(src):
             dst += c
     return dst
 
+class UUID:
+    x = "[0-9a-fA-f]"
+    uuidRE = re.compile("^(%s{8})-(%s{4})-(%s{4})-(%s{4})-(%s{4})(%s{8})$"
+                        % (x, x, x, x, x, x))
+
+    def __init__(self, value):
+        self.value = value
+
+    @staticmethod
+    def fromString(s):
+        if not uuidRE.match(s):
+            raise Error("%s is not a valid UUID" % s)
+        return UUID(s)
+
+    @staticmethod
+    def fromJson(json):
+        if UUID.isValidJson(json):
+            return UUID(json[1])
+        else:
+            raise Error("%s is not valid JSON for a UUID" % json)
+
+    @staticmethod
+    def isValidJson(json):
+        return len(json) == 2 and json[0] == "uuid" and uuidRE.match(json[1])
+            
+    def toJson(self):
+        return ["uuid", self.value]
+
+    def cInitUUID(self, var):
+        m = re.match(self.value)
+        return ["%s.parts[0] = 0x%s;" % (var, m.group(1)),
+                "%s.parts[1] = 0x%s%s;" % (var, m.group(2), m.group(3)),
+                "%s.parts[2] = 0x%s%s;" % (var, m.group(4), m.group(5)),
+                "%s.parts[3] = 0x%s;" % (var, m.group(6))]
+
+class Atom:
+    def __init__(self, type, value):
+        self.type = type
+        self.value = value
+
+    @staticmethod
+    def fromJson(type_, json):
+        if ((type_ == 'integer' and type(json) in [int, long])
+            or (type_ == 'real' and type(json) in [int, long, float])
+            or (type_ == 'boolean' and json in [True, False])
+            or (type_ == 'string' and type(json) in [str, unicode])):
+            return Atom(type_, json)
+        elif type_ == 'uuid':
+            return UUID.fromJson(json)
+        else:
+            raise Error("%s is not valid JSON for type %s" % (json, type_))
+
+    def toJson(self):
+        if self.type == 'uuid':
+            return self.value.toString()
+        else:
+            return self.value
+
+    def cInitAtom(self, var):
+        if self.type == 'integer':
+            return ['%s.integer = %d;' % (var, self.value)]
+        elif self.type == 'real':
+            return ['%s.real = %.15g;' % (var, self.value)]
+        elif self.type == 'boolean':
+            if self.value:
+                return ['%s.boolean = true;']
+            else:
+                return ['%s.boolean = false;']
+        elif self.type == 'string':
+            return ['%s.string = xstrdup("%s");'
+                    % (var, escapeCString(self.value))]
+        elif self.type == 'uuid':
+            return self.value.cInitUUID(var)        
+
 class BaseType:
     def __init__(self, type,
+                 enum=None,
                  refTable=None,
                  minInteger=None, maxInteger=None,
                  minReal=None, maxReal=None,
                  minLength=None, maxLength=None):
         self.type = type
+        self.enum = enum
         self.refTable = refTable
         self.minInteger = minInteger
         self.maxInteger = maxInteger
@@ -129,6 +205,10 @@ class BaseType:
             return BaseType(json)
         else:
             atomicType = mustGetMember(json, 'type', [unicode], description)
+            enum = getMember(json, 'enum', [], description)
+            if enum:
+                enumType = Type(atomicType, None, 0, 'unlimited')
+                enum = Datum.fromJson(enumType, enum)
             refTable = getMember(json, 'refTable', [unicode], description)
             minInteger = getMember(json, 'minInteger', [int, long], description)
             maxInteger = getMember(json, 'maxInteger', [int, long], description)
@@ -136,7 +216,7 @@ class BaseType:
             maxReal = getMember(json, 'maxReal', [int, long, float], description)
             minLength = getMember(json, 'minLength', [int], description)
             maxLength = getMember(json, 'minLength', [int], description)
-            return BaseType(atomicType, refTable, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
+            return BaseType(atomicType, enum, refTable, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
 
     def toEnglish(self):
         if self.type == 'uuid' and self.refTable:
@@ -179,6 +259,10 @@ class BaseType:
         stmts = []
         stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
                 var, self.type.upper()),)
+        if self.enum:
+            stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
+                         % (var, var))
+            stmts += self.enum.cInitDatum("%s.enum_" % var)
         if self.type == 'integer':
             if self.minInteger != None:
                 stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger))
@@ -284,6 +368,58 @@ class Type:
         initMax = "%s%s.n_max = %s;" % (indent, var, max)
         return "\n".join((initKey, initValue, initMin, initMax))
 
+class Datum:
+    def __init__(self, type, values):
+        self.type = type
+        self.values = values
+
+    @staticmethod
+    def fromJson(type_, json):
+        if not type_.value:
+            if len(json) == 2 and json[0] == "set":
+                values = []
+                for atomJson in json[1]:
+                    values += [Atom.fromJson(type_.key, atomJson)]
+            else:
+                values = [Atom.fromJson(type_.key, json)]
+        else:
+            if len(json) != 2 or json[0] != "map":
+                raise Error("%s is not valid JSON for a map" % json)
+            values = []
+            for pairJson in json[1]:
+                values += [(Atom.fromJson(type_.key, pairJson[0]),
+                            Atom.fromJson(type_.value, pairJson[1]))]
+        return Datum(type_, values)
+
+    def cInitDatum(self, var):
+        if len(self.values) == 0:
+            return ["ovsdb_datum_init_empty(%s);" % var]
+
+        s = ["%s->n = %d;" % (var, len(self.values))]
+        s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
+              % (var, len(self.values), var)]
+
+        for i in range(len(self.values)):
+            key = self.values[i]
+            if self.type.value:
+                key = key[0]
+            s += key.cInitAtom("%s->keys[%d]" % (var, i))
+        
+        if self.type.value:
+            s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
+                  % (var, len(self.values), var)]
+            for i in range(len(self.values)):
+                value = self.values[i][1]
+                s += key.cInitAtom("%s->values[%d]" % (var, i))
+        else:
+            s += ["%s->values = NULL;" % var]
+
+        if len(self.values) > 1:
+            s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
+                  % (var, self.type.key.upper())]
+
+        return s
+
 def parseSchema(filename):
     return DbSchema.fromJson(json.load(open(filename, "r")))
 
index 07244a8..045b1c3 100644 (file)
@@ -590,6 +590,9 @@ AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=-1])],
 AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=4096])], 
   [1], [], [ovs-vsctl: constraint violation: 4096 is not in the valid range 0 to 4095 (inclusive)
 ], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set c br1 'connection-mode=xyz'])], 
+  [1], [], [[ovs-vsctl: constraint violation: xyz is not one of the allowed values ([in-band, out-of-band])
+]], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set c br1 connection-mode:x=y])], 
   [1], [], [ovs-vsctl: cannot specify key to set for non-map column connection_mode
 ], [OVS_VSCTL_CLEANUP])
index 031eee0..4bfd909 100644 (file)
@@ -198,7 +198,80 @@ OVSDB_CHECK_NEGATIVE([uuids must be valid],
   [parse-atom-strings '[["uuid"]]' '1234-5678'],
   ["1234-5678" is not a valid UUID])
 \f
-AT_BANNER([OVSDB -- atoms with constraints])
+AT_BANNER([OVSDB -- atoms with enum constraints])
+
+OVSDB_CHECK_POSITIVE([integer atom enum], 
+  [[parse-atoms '[{"type": "integer", "enum": ["set", [1, 6, 8, 10]]}]' \
+    '[0]' \
+    '[1]' \
+    '[2]' \
+    '[3]' \
+    '[6]' \
+    '[7]' \
+    '[8]' \
+    '[9]' \
+    '[10]' \
+    '[11]']], 
+  [[constraint violation: 0 is not one of the allowed values ([1, 6, 8, 10])
+1
+constraint violation: 2 is not one of the allowed values ([1, 6, 8, 10])
+constraint violation: 3 is not one of the allowed values ([1, 6, 8, 10])
+6
+constraint violation: 7 is not one of the allowed values ([1, 6, 8, 10])
+8
+constraint violation: 9 is not one of the allowed values ([1, 6, 8, 10])
+10
+constraint violation: 11 is not one of the allowed values ([1, 6, 8, 10])]])
+
+OVSDB_CHECK_POSITIVE([real atom enum], 
+  [[parse-atoms '[{"type": "real", "enum": ["set", [-1.5, 1.5]]}]' \
+    '[-2]' \
+    '[-1]' \
+    '[-1.5]' \
+    '[0]' \
+    '[1]' \
+    '[1.5]' \
+    '[2]']], 
+  [[constraint violation: -2 is not one of the allowed values ([-1.5, 1.5])
+constraint violation: -1 is not one of the allowed values ([-1.5, 1.5])
+-1.5
+constraint violation: 0 is not one of the allowed values ([-1.5, 1.5])
+constraint violation: 1 is not one of the allowed values ([-1.5, 1.5])
+1.5
+constraint violation: 2 is not one of the allowed values ([-1.5, 1.5])]])
+
+OVSDB_CHECK_POSITIVE([boolean atom enum], 
+  [[parse-atoms '[{"type": "boolean", "enum": false}]' \
+    '[false]' \
+    '[true]']], 
+  [[false
+constraint violation: true is not one of the allowed values ([false])]])
+
+OVSDB_CHECK_POSITIVE([string atom enum], 
+  [[parse-atoms '[{"type": "string", "enum": ["set", ["abc", "def"]]}]' \
+    '[""]' \
+    '["ab"]' \
+    '["abc"]' \
+    '["def"]' \
+    '["defg"]' \
+    '["DEF"]']], 
+  [[constraint violation: "" is not one of the allowed values ([abc, def])
+constraint violation: ab is not one of the allowed values ([abc, def])
+"abc"
+"def"
+constraint violation: defg is not one of the allowed values ([abc, def])
+constraint violation: DEF is not one of the allowed values ([abc, def])]])
+
+OVSDB_CHECK_POSITIVE([uuid atom enum], 
+  [[parse-atoms '[{"type": "uuid", "enum": ["set", [["uuid", "6d53a6dd-2da7-4924-9927-97f613812382"], ["uuid", "52cbc842-137a-4db5-804f-9f34106a0ba3"]]]}]' \
+    '["uuid", "6d53a6dd-2da7-4924-9927-97f613812382"]' \
+    '["uuid", "52cbc842-137a-4db5-804f-9f34106a0ba3"]' \
+    '["uuid", "dab2a6b2-6094-4f43-a7ef-4c0f0608f176"]']], 
+  [[["uuid","6d53a6dd-2da7-4924-9927-97f613812382"]
+["uuid","52cbc842-137a-4db5-804f-9f34106a0ba3"]
+constraint violation: dab2a6b2-6094-4f43-a7ef-4c0f0608f176 is not one of the allowed values ([52cbc842-137a-4db5-804f-9f34106a0ba3, 6d53a6dd-2da7-4924-9927-97f613812382])]])
+\f
+AT_BANNER([OVSDB -- atoms with other constraints])
 
 OVSDB_CHECK_POSITIVE([integers >= 5], 
   [[parse-atoms '[{"type": "integer", "minInteger": 5}]' \
index 2d98183..7122e9d 100644 (file)
@@ -15,6 +15,9 @@ OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type],
 
 AT_BANNER([OVSDB -- base types])
 
+OVSDB_CHECK_POSITIVE([integer enum],
+  [[parse-base-type '{"type": "integer", "enum": ["set", [-1, 4, 5]]}' ]],
+  [[{"enum":["set",[-1,4,5]],"type":"integer"}]])
 OVSDB_CHECK_POSITIVE([integer >= 5], 
   [[parse-base-type '{"type": "integer", "minInteger": 5}' ]],
   [{"minInteger":5,"type":"integer"}])
@@ -28,6 +31,9 @@ OVSDB_CHECK_NEGATIVE([integer max may not be less than min],
   [[parse-base-type '{"type": "integer", "minInteger": 5, "maxInteger": 3}']],
   [minInteger exceeds maxInteger])
 
+OVSDB_CHECK_POSITIVE([real enum],
+  [[parse-base-type '{"type": "real", "enum": ["set", [1.5, 0, 2.75]]}' ]],
+  [[{"enum":["set",[0,1.5,2.75]],"type":"real"}]])
 OVSDB_CHECK_POSITIVE([real >= -1.5], 
   [[parse-base-type '{"type": "real", "minReal": -1.5}']],
   [{"minReal":-1.5,"type":"real"}])
@@ -43,7 +49,13 @@ OVSDB_CHECK_NEGATIVE([real max may not be less than min],
 
 OVSDB_CHECK_POSITIVE([boolean], 
   [[parse-base-type '[{"type": "boolean"}]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([boolean enum],
+  [[parse-base-type '{"type": "boolean", "enum": true}' ]],
+  [[{"enum":true,"type":"boolean"}]])
 
+OVSDB_CHECK_POSITIVE([string enum], 
+  [[parse-base-type '{"type": "string", "enum": ["set", ["def", "abc"]]}']],
+  [[{"enum":["set",["abc","def"]],"type":"string"}]])
 OVSDB_CHECK_POSITIVE([string minLength], 
   [[parse-base-type '{"type": "string", "minLength": 1}']],
   [{"minLength":1,"type":"string"}])
@@ -60,6 +72,9 @@ OVSDB_CHECK_NEGATIVE([maxLength must not be negative],
   [[parse-base-type '{"type": "string", "maxLength": -1}']],
   [maxLength out of valid range 0 to 4294967295])
 
+OVSDB_CHECK_POSITIVE([uuid enum], 
+  [[parse-base-type '{"type": "uuid", "enum": ["uuid", "36bf19c0-ad9d-4232-bb85-b3d73dfe2123"]}' ]],
+  [[{"enum":["uuid","36bf19c0-ad9d-4232-bb85-b3d73dfe2123"],"type":"uuid"}]])
 OVSDB_CHECK_POSITIVE([uuid refTable], 
   [[parse-base-type '{"type": "uuid", "refTable": "myTable"}' ]],
   [{"refTable":"myTable","type":"uuid"}])
index b75c100..ac42edf 100644 (file)
          "type": {"key": "integer", "min": 0, "max": 1}},
        "fail_mode": {
          "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.",
-         "type": {"key": {"type": "string"},
+         "type": {"key": {"type": "string",
+                          "enum": ["set", ["standalone", "secure"]]},
                   "min": 0, "max": 1}},
        "discover_accept_regex": {
          "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated.  If not specified, the default is implementation-specific.",
          "type": {"key": "boolean", "min": 0, "max": 1}},
        "connection_mode": {
          "comment": "Either \"in-band\" or \"out-of-band\".  If not specified, the default is implementation-specific.",
-         "type": {"key": {"type": "string"},
+         "type": {"key": {"type": "string",
+                  "enum": ["set", ["in-band", "out-of-band"]]},
                   "min": 0, "max": 1}},
        "local_ip": {
          "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.",