Jelajahi Sumber

feat(restapi): support validate rule

Signed-off-by: Rui-Gan <1171530954@qq.com>
Regina 1 tahun lalu
induk
melakukan
c72afece7b

+ 20 - 0
docs/en_US/api/restapi/rules.md

@@ -188,3 +188,23 @@ Response Sample:
   }
 }
 ```
+
+## validate a rule
+
+The API accepts a JSON content and validate a rule.
+
+```shell
+POST http://localhost:9081/rules/validate
+```
+
+Request Sample
+
+```json
+{
+  "id": "rule1",
+  "sql": "SELECT * FROM demo",
+  "actions": [{
+    "log":  {}
+  }]
+}
+```

+ 20 - 0
docs/zh_CN/api/restapi/rules.md

@@ -159,3 +159,23 @@ GET http://localhost:9081/rules/{id}/status
     ...
 }
 ```
+
+## 验证规则
+
+该 API 用于验证规则。
+
+```shell
+POST http://localhost:9081/rules/validate
+```
+
+请求示例:
+
+```json
+{
+  "id": "rule1",
+  "sql": "SELECT * FROM demo",
+  "actions": [{
+    "log":  {}
+  }]
+}
+```

+ 18 - 0
internal/server/rest.go

@@ -154,6 +154,7 @@ func createRestServer(ip string, port int, needToken bool) *http.Server {
 	r.HandleFunc("/rules/{name}/stop", stopRuleHandler).Methods(http.MethodPost)
 	r.HandleFunc("/rules/{name}/restart", restartRuleHandler).Methods(http.MethodPost)
 	r.HandleFunc("/rules/{name}/topo", getTopoRuleHandler).Methods(http.MethodGet)
+	r.HandleFunc("/rules/validate", validateRuleHandler).Methods(http.MethodPost)
 	r.HandleFunc("/ruleset/export", exportHandler).Methods(http.MethodPost)
 	r.HandleFunc("/ruleset/import", importHandler).Methods(http.MethodPost)
 	r.HandleFunc("/configs", configurationUpdateHandler).Methods(http.MethodPatch)
@@ -631,6 +632,23 @@ func getTopoRuleHandler(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte(content))
 }
 
+// validate a rule
+func validateRuleHandler(w http.ResponseWriter, r *http.Request) {
+	defer r.Body.Close()
+	body, err := io.ReadAll(r.Body)
+	if err != nil {
+		handleError(w, err, "Invalid body", logger)
+		return
+	}
+	validate, err := validateRule("", string(body))
+	w.WriteHeader(http.StatusOK)
+	if !validate {
+		w.Write([]byte(err.Error()))
+		return
+	}
+	w.Write([]byte("The rule has been successfully validated and is confirmed to be correct."))
+}
+
 type rulesetInfo struct {
 	Content  string `json:"content"`
 	FilePath string `json:"file"`

+ 29 - 4
internal/server/rest_test.go

@@ -75,6 +75,7 @@ func (suite *RestTestSuite) SetupTest() {
 	r.HandleFunc("/rules/{name}/stop", stopRuleHandler).Methods(http.MethodPost)
 	r.HandleFunc("/rules/{name}/restart", restartRuleHandler).Methods(http.MethodPost)
 	r.HandleFunc("/rules/{name}/topo", getTopoRuleHandler).Methods(http.MethodGet)
+	r.HandleFunc("/rules/validate", validateRuleHandler).Methods(http.MethodPost)
 	r.HandleFunc("/ruleset/export", exportHandler).Methods(http.MethodPost)
 	r.HandleFunc("/ruleset/import", importHandler).Methods(http.MethodPost)
 	r.HandleFunc("/configs", configurationUpdateHandler).Methods(http.MethodPatch)
@@ -217,13 +218,37 @@ func (suite *RestTestSuite) Test_rulesManageHandler() {
 	w1 := httptest.NewRecorder()
 	suite.r.ServeHTTP(w1, req1)
 
-	// create rule with trigger false
+	// validate a rule
 	ruleJson := `{"id": "rule1","triggered": false,"sql": "select * from alert","actions": [{"log": {}}]}`
 
 	buf2 := bytes.NewBuffer([]byte(ruleJson))
-	req2, _ := http.NewRequest(http.MethodPost, "http://localhost:8080/rules", buf2)
+	req2, _ := http.NewRequest(http.MethodPost, "http://localhost:8080/rules/validate", buf2)
 	w2 := httptest.NewRecorder()
 	suite.r.ServeHTTP(w2, req2)
+	returnVal, _ := io.ReadAll(w2.Result().Body)
+	expect := `The rule has been successfully validated and is confirmed to be correct.`
+	assert.Equal(suite.T(), http.StatusOK, w2.Code)
+	assert.Equal(suite.T(), expect, string(returnVal))
+
+	// valiadate a wrong rule
+	ruleJson = `{"id": "rule1", "sql": "select * from alert"}`
+
+	buf2 = bytes.NewBuffer([]byte(ruleJson))
+	req2, _ = http.NewRequest(http.MethodPost, "http://localhost:8080/rules/validate", buf2)
+	w2 = httptest.NewRecorder()
+	suite.r.ServeHTTP(w2, req2)
+	returnVal, _ = io.ReadAll(w2.Result().Body)
+	expect = `invalid rule json: Missing rule actions.`
+	assert.Equal(suite.T(), http.StatusOK, w2.Code)
+	assert.Equal(suite.T(), expect, string(returnVal))
+
+	// create rule with trigger false
+	ruleJson = `{"id": "rule1","triggered": false,"sql": "select * from alert","actions": [{"log": {}}]}`
+
+	buf2 = bytes.NewBuffer([]byte(ruleJson))
+	req2, _ = http.NewRequest(http.MethodPost, "http://localhost:8080/rules", buf2)
+	w2 = httptest.NewRecorder()
+	suite.r.ServeHTTP(w2, req2)
 
 	// get all rules
 	req3, _ := http.NewRequest(http.MethodGet, "http://localhost:8080/rules", bytes.NewBufferString("any"))
@@ -257,8 +282,8 @@ func (suite *RestTestSuite) Test_rulesManageHandler() {
 	w1 = httptest.NewRecorder()
 	suite.r.ServeHTTP(w1, req1)
 
-	returnVal, _ := io.ReadAll(w1.Result().Body)
-	expect := `{"id": "rule1","triggered": false,"sql": "select * from alert","actions": [{"nop": {}}]}`
+	returnVal, _ = io.ReadAll(w1.Result().Body)
+	expect = `{"id": "rule1","triggered": false,"sql": "select * from alert","actions": [{"nop": {}}]}`
 	assert.Equal(suite.T(), expect, string(returnVal))
 
 	// get rule status

+ 9 - 0
internal/server/rule_manager.go

@@ -305,3 +305,12 @@ func getRuleTopo(name string) (string, error) {
 		return "", errorx.NewWithCode(errorx.NOT_FOUND, fmt.Sprintf("Rule %s is not found", name))
 	}
 }
+
+func validateRule(name, ruleJson string) (bool, error) {
+	// Validate the rule json
+	_, err := ruleProcessor.GetRuleByJson(name, ruleJson)
+	if err != nil {
+		return false, fmt.Errorf("invalid rule json: %v", err)
+	}
+	return true, nil
+}