commit 717158afe716515b43cfc01d5ad45b3d7ad7766e
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Fri Oct 16 01:09:36 2009 +0200

    [evol] improve config #10 (improved object definition check)

diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/fs.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/fs.conf
index 49f4a1e..10d632c 100644
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/fs.conf
+++ b/conf/ldap_shadows/shadows/MilkyPond/aspects/fs.conf
@@ -4,12 +4,12 @@ presentation:
   associated_relations: ['primaryGroup', 'secondaryGroups']
 relations:
   primaryGroup:
-    type: :belongs_to
+    type: belongs_to
     object: group
     foreign_key: gidNumber
     primary_key: gidNumber
   secondaryGroups:
-    type: :belongs_to
+    type: belongs_to
     object: group
     many: uniqueMember
     foreign_key: dn
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
index 77d800b..b6d740e 100644
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
+++ b/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
@@ -10,12 +10,12 @@ presentation:
   associated_relations: ['foundersIndividuals', 'foundersEntities']
 relations:
   foundersIndividuals:
-    type: :has_many
+    type: has_many
     object: individual
     foreign_key: founder
     primary_key: dn
   foundersEntities:
-    type: :has_many
+    type: has_many
     object: entity
     foreign_key: founder
     primary_key: dn
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/group.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/group.conf
index 7a377b9..0c57e39 100644
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/group.conf
+++ b/conf/ldap_shadows/shadows/MilkyPond/objects/group.conf
@@ -10,22 +10,22 @@ presentation:
   associated_relations: ['individualsPrimaryMembers', 'botsPrimaryMembers', 'individualsSecondaryMembers', 'botsSecondaryMembers']
 relations:
   individualsPrimaryMembers:
-    type: :belongs_to
+    type: belongs_to
     object: individual
     many: gidNumber
     foreign_key: gidNumber
   botsPrimaryMembers:
-    type: :belongs_to
+    type: belongs_to
     object: bot
     many: gidNumber
     foreign_key: gidNumber
   individualsSecondaryMembers:
-    type: :has_many
+    type: has_many
     object: individual
     foreign_key: uniqueMember
     primary_key: dn
   botsSecondaryMembers:
-    type: :has_many
+    type: has_many
     object: bot
     foreign_key: uniqueMember
     primary_key: dn
diff --git a/data/ldap_shadows/schema/object.yaml b/data/ldap_shadows/schema/object.yaml
index 62ec9ac..9c4baa1 100644
--- a/data/ldap_shadows/schema/object.yaml
+++ b/data/ldap_shadows/schema/object.yaml
@@ -3,13 +3,14 @@ type: map
 mapping:
   "mapping":
     type: map
-    required: yes
+    required: true
     mapping:
       =:
-        type: str
+        type: any
   "presentation":
     type: map
     mapping:
+      "name_attribute": {type: str}
       "allowed_aspects":
         type: seq
         sequence:
@@ -22,4 +23,20 @@ mapping:
         type: seq
         sequence:
           - type: str
+      "associated_relations":
+        type: seq
+        sequence:
+          - type: str
+  "relations":
+    type: map
+    mapping:
+      =:
+        type: map
+        name: RelationMapping
+        mapping:
+          "type": {type: str, required: true, enum: [belongs_to, has_many]}
+          "object": {type: str, required: true}
+          "foreign_key": {type: str, required: true}
+          "many": {type: str}
+          "primary_key": {type: str}
 
diff --git a/lib/ldap_shadows.rb b/lib/ldap_shadows.rb
index af2f18d..e936242 100644
--- a/lib/ldap_shadows.rb
+++ b/lib/ldap_shadows.rb
@@ -22,6 +22,7 @@ require 'ldap_shadows/lang_additions'
 require 'ldap_shadows/exceptions'
 require 'ldap_shadows/controller'
 require 'gettext'
+require 'pp'
 
 
 module LdapShadows
diff --git a/lib/ldap_shadows/controller.rb b/lib/ldap_shadows/controller.rb
index 2f1d90f..2e106bf 100755
--- a/lib/ldap_shadows/controller.rb
+++ b/lib/ldap_shadows/controller.rb
@@ -58,6 +58,10 @@ module LdapShadows
     end
 
     def set_aspect(aspect_name, aspect_def)
+      parser = Kwalify::Yaml::Parser.new
+      aspect_def = parser.parse(aspect_def, aspect_name)
+      aspect_def.recursive_symbolize_keys!
+      aspect_name = aspect_name.to_sym
       @aspects[aspect_name] = aspect_def
 
       filename = File.join(Config::CFG_DIR, "hooks", "aspects", aspect_name.to_s.downcase + ".rb")
@@ -90,10 +94,39 @@ module LdapShadows
       "LdapObject" + obj_name.to_s.capitalize
     end
 
+    class ObjectValidator < Kwalify::Validator
+      ## hook method called by Validator#validate()
+      def validate_hook(value, rule, path, errors)
+        case rule.name
+        when 'RelationMapping'
+          if value['type'] == 'belongs_to'
+            unless value.has_key? 'many'
+              msg = "a 'belongs_to' relation needs a 'many' parameter"
+              errors << Kwalify::ValidationError.new(msg, path)
+            end
+          else
+            unless value.has_key? 'primary_key'
+              msg = "a 'has_many' relation needs a 'primary_key' parameter"
+              errors << Kwalify::ValidationError.new(msg, path)
+            end
+          end
+        end
+      end
+    end
+
     def load_object(obj_name, obj_def)
-      schema = load_schema("object")
-      validator = Kwalify::Validator.new(schema)
-      errors = validator.validate(obj_def)
+      schema_name = 'object'
+      schema = load_schema(schema_name)
+
+      # validate config with schema
+      validator = ObjectValidator.new(schema)
+      parser = Kwalify::Yaml::Parser.new(validator)
+      obj_def = parser.parse(obj_def, obj_name)
+      errors = parser.errors()
+      raise_if_validation_errors("#{schema_name.capitalize} '#{obj_name}'", errors)
+
+      obj_name = obj_name.to_sym
+      obj_def.recursive_symbolize_keys!
 
       obj_mapping = obj_def[:mapping]
       klass_name = self.class.object_name_to_klass_name(obj_name)
@@ -147,9 +180,9 @@ module LdapShadows
           rel[:class_name] = foreign_klass.to_s
 
           case rel[:type]
-          when :belongs_to
+          when 'belongs_to'
             klass.belongs_to field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_BELONGS_TO_OPTIONS.include?(key) }
-          when :has_many
+          when 'has_many'
             klass.has_many field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_HAS_MANY_OPTIONS.include?(key) }
           else
             raise "bug in '#{obj_name}' object relations (wrong type)"
@@ -242,9 +275,8 @@ module LdapShadows
       Dir.glob(c_config_pattern).each do |f|
         next if f[0..0] == "."
 
-        c_name = File.basename(f).sub(".conf", "").to_sym
-        c_config = YAML.load(IO.read(f))
-        c_config.recursive_symbolize_keys!
+        c_name = File.basename(f).sub(".conf", "")
+        c_config = File.read(f)
 
         yield(c_name, c_config)
       end
@@ -254,13 +286,29 @@ module LdapShadows
         schema = @schema[type]
         if schema.nil?
           schema_file = File.join(Config::DATA_DIR, "schema", type + ".yaml")
-          schema = YAML.load(IO.read(schema_file))
+          schema = YAML.load_file(schema_file)
+
+          # validate schema
+          metavalidator = Kwalify::MetaValidator.instance
+          errors = metavalidator.validate(schema)
+          raise_if_validation_errors("'#{type}' schema", errors)
+
           @schema[type] = schema
         end
         schema
     rescue
       raise PreProcessingError, _("Could not load schema: %s") % $!
     end
+
+    def raise_if_validation_errors(name, errors)
+      if errors and not errors.empty?
+        err_msg = []
+        for e in errors
+          err_msg << "[#{e.path}] #{e.message}"
+        end
+        raise PreProcessingError, _("%s is not valid:\n%s") % [name, err_msg.join("\n")]
+      end
+    end
   end
 
   # default location for mapped objects
