From 565f920b1005af55755751587bc498e435744a42 Mon Sep 17 00:00:00 2001 From: Florian Beisel Date: Tue, 6 Feb 2024 21:05:44 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20integrates=20Dynamic=20ENUM=20Metad?= =?UTF-8?q?ata=20within=20Rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This commit revolutionizes our approach to handling ENUM metadata for rule input structures by embedding this metadata directly within each rule's definition. By leveraging the `EnumProvider` interface and utilizing reflection, we've created a system where rules self-describe their ENUM fields, enhancing the API's flexibility and maintainability. The `GetRuleStruct` API endpoint is now capable of dynamically generating responses that include both the input structure and ENUM options, directly derived from the rule definitions themselves. Key Changes: - **Introduced `EnumProvider` Interface**: Allows rule structs to define their own ENUM options, centralizing this information within the rule implementations. - **Enhanced Rule Descriptors**: Rule descriptors are streamlined to focus on factory methods and input struct types, with ENUM metadata being provided by the rules themselves through the `EnumProvider` interface. - **Dynamic ENUM Metadata Retrieval**: The `GetRuleStruct` function dynamically extracts ENUM metadata from rules that implement the `EnumProvider`, ensuring the API response includes relevant ENUM options for frontend UI generation. - **API Response Enhancement**: Adjusted the API to provide a response that includes the rule's input structure, possible ENUM values, and the rule instance representation, all derived dynamically to support extensible and maintainable rule definitions. --- api/rule_handlers.go | 27 ++++++++++++++++++------ rules/enum.go | 25 ++++++++++++++++++++++ rules/registry.go | 5 +++++ rules/server.go | 50 ++++++++++++++++++++++++++------------------ 4 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 rules/enum.go diff --git a/api/rule_handlers.go b/api/rule_handlers.go index 10de347..a3a9146 100644 --- a/api/rule_handlers.go +++ b/api/rule_handlers.go @@ -56,24 +56,38 @@ func getHostnameRuleByCategory(category string) (rules.HostnameRule, error) { // @Router /rules/:rule [get] func GetRuleStruct(c *gin.Context) { ruleName := c.Param("rule") - descriptor, exists := rules.RulesRegistry[ruleName] if !exists { c.JSON(http.StatusNotFound, gin.H{"error": "Rule not found"}) return } - // Create instances of the rule and its input struct + // Create an instance of the rule struct for output ruleInstance := descriptor.Factory() - inputInstance := reflect.New(reflect.TypeOf(ruleInstance).Elem()).Interface() - // Serialize instances to JSON + // Check if the rule instance implements EnumProvider for ENUM metadata + enumProvider, implements := ruleInstance.(rules.EnumProvider) + var enumInfo map[string][]string + if implements { + // Retrieve ENUM metadata from the rule instance + enumMetadata := enumProvider.EnumOptions() + enumInfo = make(map[string][]string) + for _, meta := range enumMetadata { + enumInfo[meta.FieldName] = meta.Values + } + } + + // Serialize the rule instance to JSON for output ruleJSON, err := rules.StructToJSON(ruleInstance) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error serializing rule struct"}) return } + // Create an instance of the input struct for serialization + inputInstance := reflect.New(descriptor.InputStruct).Elem().Interface() + + // Serialize the input struct instance to JSON inputJSON, err := rules.StructToJSON(inputInstance) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error serializing input struct"}) @@ -81,7 +95,8 @@ func GetRuleStruct(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "input": inputJSON, - "output": ruleJSON, + "input": inputJSON, // Input struct with possible ENUM values + "enums": enumInfo, // ENUM metadata if available + "output": ruleJSON, // Serialized rule instance }) } diff --git a/rules/enum.go b/rules/enum.go new file mode 100644 index 0000000..6c62e30 --- /dev/null +++ b/rules/enum.go @@ -0,0 +1,25 @@ +// Copyright 2024 Florian Beisel +// +// 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. + +package rules + +type EnumFieldMetadata struct { + FieldName string + Values []string +} + +// EnumProvider interface that rules should implement if they provide enum values. +type EnumProvider interface { + EnumOptions() []EnumFieldMetadata +} diff --git a/rules/registry.go b/rules/registry.go index 2e609e0..00457e2 100644 --- a/rules/registry.go +++ b/rules/registry.go @@ -14,21 +14,26 @@ package rules +import "reflect" + type RuleFactoryFunc func() HostnameRule type RuleDescriptor struct { Description string Factory RuleFactoryFunc + InputStruct reflect.Type } var RulesRegistry = map[string]RuleDescriptor{ "notebook": { Description: "Generates hostnames for notebooks.", Factory: func() HostnameRule { return &NotebookRule{} }, + InputStruct: reflect.TypeOf(NotebookRuleInput{}), }, "server": { Description: "Generates hostnames for servers.", Factory: func() HostnameRule { return &ServerRule{} }, + InputStruct: reflect.TypeOf(ServerRuleInput{}), }, // ... other rules ... } diff --git a/rules/server.go b/rules/server.go index 885c37c..6351408 100644 --- a/rules/server.go +++ b/rules/server.go @@ -43,6 +43,16 @@ type ServerRuleInput struct { Responsible string `json:"Responsible"` } +func (sr *ServerRule) EnumOptions() []EnumFieldMetadata { + return []EnumFieldMetadata{ + { + FieldName: "OrgUnit", + Values: []string{"IDG", "IDE", "SSG"}, + }, + // Add more fields as necessary + } +} + // Ensure that ServerRule implements HostnameRule interface var _ HostnameRule = &ServerRule{} @@ -108,32 +118,32 @@ func (sr *ServerRule) Generate(params map[string]interface{}) (string, []byte, e } -// @Summary Generate hostname for category "notebook" -// @Description Generates a hostname for a notebook based on dynamic rules. -// @ID insert-server-hostname -// @Accept json -// @Produce json -// @Tags Generating Hostnames -// @Param body body ServerRuleInput true "Input data to generate hostname" -// @Success 200 {object} models.SimpleHostnameResponse "Hostname" -// @Router /api/server [post] -// @Security Bearer +// @Summary Generate hostname for category "notebook" +// @Description Generates a hostname for a notebook based on dynamic rules. +// @ID insert-server-hostname +// @Accept json +// @Produce json +// @Tags Generating Hostnames +// @Param body body ServerRuleInput true "Input data to generate hostname" +// @Success 200 {object} models.SimpleHostnameResponse "Hostname" +// @Router /api/server [post] +// @Security Bearer func (nr *ServerRule) Insert(category string, params map[string]interface{}) (string, error) { // Generate the hostname return nr.baseInsert(nr, category, params) } -// @Summary Update hostname for category "notebook" -// @Description Generates a new hostname for a notebook based on dynamic rules. -// @ID update-server-hostname -// @Accept json -// @Produce json -// @Tags Manipulate existing Hostnames -// @Param body body ServerRuleInput true "Input data to generate hostname" -// @Success 200 {object} models.SimpleHostnameResponse "Hostname" -// @Router /api/server [put] -// @Security Bearer +// @Summary Update hostname for category "notebook" +// @Description Generates a new hostname for a notebook based on dynamic rules. +// @ID update-server-hostname +// @Accept json +// @Produce json +// @Tags Manipulate existing Hostnames +// @Param body body ServerRuleInput true "Input data to generate hostname" +// @Success 200 {object} models.SimpleHostnameResponse "Hostname" +// @Router /api/server [put] +// @Security Bearer func (nr *ServerRule) Update(category string, oldhostname string, params map[string]interface{}) (string, error) { return nr.baseUpdate(nr, category, oldhostname, params) }