From 47d5d84f894ef59c2c2636b1e0045c591fb3a59a Mon Sep 17 00:00:00 2001 From: agherasie Date: Tue, 16 Jul 2024 19:59:49 +0200 Subject: [PATCH 01/20] feat(examples): gno-forms --- examples/gno.land/p/demo/forms/create.gno | 69 ++++++++ .../gno.land/p/demo/forms/create_test.gno | 81 ++++++++++ examples/gno.land/p/demo/forms/forms.gno | 61 ++++++++ examples/gno.land/p/demo/forms/gno.mod | 7 + examples/gno.land/p/demo/forms/submit.gno | 37 +++++ .../gno.land/p/demo/forms/submit_test.gno | 56 +++++++ examples/gno.land/p/demo/forms/validate.gno | 148 ++++++++++++++++++ .../gno.land/p/demo/forms/validate_test.gno | 110 +++++++++++++ examples/gno.land/r/demo/forms/forms.gno | 126 +++++++++++++++ examples/gno.land/r/demo/forms/gno.mod | 7 + 10 files changed, 702 insertions(+) create mode 100644 examples/gno.land/p/demo/forms/create.gno create mode 100644 examples/gno.land/p/demo/forms/create_test.gno create mode 100644 examples/gno.land/p/demo/forms/forms.gno create mode 100644 examples/gno.land/p/demo/forms/gno.mod create mode 100644 examples/gno.land/p/demo/forms/submit.gno create mode 100644 examples/gno.land/p/demo/forms/submit_test.gno create mode 100644 examples/gno.land/p/demo/forms/validate.gno create mode 100644 examples/gno.land/p/demo/forms/validate_test.gno create mode 100644 examples/gno.land/r/demo/forms/forms.gno create mode 100644 examples/gno.land/r/demo/forms/gno.mod diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno new file mode 100644 index 00000000000..746688144a1 --- /dev/null +++ b/examples/gno.land/p/demo/forms/create.gno @@ -0,0 +1,69 @@ +package forms + +import ( + "time" + "std" + + "gno.land/p/demo/ufmt" + "gno.land/p/demo/json" +) + +func (form *Form) AddField(label string, fieldType string, required bool) { + field := Field{ + Label: label, + FieldType: fieldType, + Required: required, + } + + form.Fields = append(form.Fields, field) +} + +func (db *FormDatabase) GetForm(id string) *Form { + for _, form := range db.Forms { + if form.ID == id { + return form + } + } + return nil +} + +func (db *FormDatabase) CreateForm(title string, description string, openAt string, closeAt string, data string) string { + id := db.IDCounter.Next().String() + + form := &Form{ + ID: id, + Owner: std.PrevRealm().Addr(), + Title: title, + Description: description, + CreatedAt: time.Now().String(), + OpenAt: openAt, + CloseAt: closeAt, + } + + db.Forms = append(db.Forms, form) + + node, err := json.Unmarshal([]byte(data)) + if err != nil { + ufmt.Errorf("error: %v", err) + } + + for i := 0; i < node.Size(); i++ { + field, err := node.GetIndex(i) + + if err != nil { + ufmt.Errorf("error: %v", err) + } + + labelNode, _ := field.GetKey("label") + fieldTypeNode, _ := field.GetKey("fieldType") + requiredNode, _ := field.GetKey("required") + + label, _ := labelNode.GetString() + fieldType, _ := fieldTypeNode.GetString() + required, _ := requiredNode.GetBool() + + form.AddField(label, fieldType, required) + } + + return id +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/create_test.gno b/examples/gno.land/p/demo/forms/create_test.gno new file mode 100644 index 00000000000..b26f60d65f1 --- /dev/null +++ b/examples/gno.land/p/demo/forms/create_test.gno @@ -0,0 +1,81 @@ +package forms + +import ( + "testing" + "std" + + "gno.land/p/demo/testutils" +) + +func TestCreateForm(t *testing.T) { + alice := testutils.TestAddress("alice") + std.TestSetOrigCaller(alice) + + title := "Simple Form" + description := "This is a form" + openAt := "2021-01-01T00:00:00Z" + closeAt := "2021-01-02T00:00:00Z" + data := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + }, + { + "label": "Age", + "fieldType": "number", + "required": false + }, + { + "label": "Is this a test?", + "fieldType": "boolean", + "required": false + }, + { + "label": "Favorite Food", + "fieldType": "['Pizza', 'Schnitzel', 'Burger']", + "required": true + }, + { + "label": "Favorite Foods", + "fieldType": "{'Pizza', 'Schnitzel', 'Burger'}", + "required": true + } + ]` + db := NewDatabase() + id := db.CreateForm(title, description, openAt, closeAt, data) + if id == "" { + t.Error("Form ID is empty") + } + form := db.GetForm(id) + if form == nil { + t.Error("Form is nil") + } + if form.Owner != alice { + t.Error("Owner is not correct") + } + if form.Title != title { + t.Error("Title is not correct") + } + if form.Description != description { + t.Error("Description is not correct") + } + if len(form.Fields) != 5 { + t.Error("Fields are not correct") + } + if form.Fields[0].Label != "Name" { + t.Error("Field 0 label is not correct") + } + if form.Fields[0].FieldType != "string" { + t.Error("Field 0 type is not correct") + } + if form.Fields[0].Required != true { + t.Error("Field 0 required is not correct") + } + if form.Fields[1].Label != "Age" { + t.Error("Field 1 label is not correct") + } + if form.Fields[1].FieldType != "number" { + t.Error("Field 1 type is not correct") + } +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno new file mode 100644 index 00000000000..5e64aedc766 --- /dev/null +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -0,0 +1,61 @@ +package forms + +import ( + "std" + + "gno.land/p/demo/seqid" +) + +type Field struct { + Label string + + /* + string: "string"; + number: "number"; + boolean: "boolean"; + choice: "['Pizza', 'Schnitzel', 'Burger']"; + multi-choice: "{'Pizza', 'Schnitzel', 'Burger'}"; + */ + FieldType string + + Required bool +} + +type Form struct { + ID string + Owner std.Address + + Title string + Description string + Fields []Field + + CreatedAt string + OpenAt string + CloseAt string +} + +type Submission struct { + FormID string + + Author std.Address + + /* ["Alex", 21, true, 0, [0, 1]] */ + Answers string // json + + SubmittedAt string +} + +type FormDatabase struct { + Forms []*Form + + Answers []*Submission + + IDCounter seqid.ID +} + +func NewDatabase() *FormDatabase { + return &FormDatabase{ + Forms: make([]*Form, 0), + Answers: make([]*Submission, 0), + } +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/gno.mod b/examples/gno.land/p/demo/forms/gno.mod new file mode 100644 index 00000000000..0a9eb6dbfc8 --- /dev/null +++ b/examples/gno.land/p/demo/forms/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/demo/forms + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest +) \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/submit.gno b/examples/gno.land/p/demo/forms/submit.gno new file mode 100644 index 00000000000..1395cb59682 --- /dev/null +++ b/examples/gno.land/p/demo/forms/submit.gno @@ -0,0 +1,37 @@ +package forms + +import ( + "time" + "std" + + "gno.land/p/demo/ufmt" +) + +func (db *FormDatabase) SubmitForm(formID string, answers string) { + form := db.GetForm(formID) + if form == nil { + panic(ufmt.Errorf("Form not found: %s", formID)) + } + + if ValidateAnswers(answers, form.Fields) == false { + panic("Invalid answers") + } + + answer := &Submission{ + FormID: formID, + Answers: answers, + Author: std.PrevRealm().Addr(), + SubmittedAt: time.Now().String(), + } + + db.Answers = append(db.Answers, answer) +} + +func (db *FormDatabase) GetAnswer(formID string, authorID string) *Submission { + for _, answer := range db.Answers { + if answer.FormID == formID && answer.Author.String() == authorID { + return answer + } + } + return nil +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno new file mode 100644 index 00000000000..20365bcf734 --- /dev/null +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -0,0 +1,56 @@ +package forms + +import ( + "testing" + + "gno.land/p/demo/testutils" +) + +func TestAnswerForm(t *testing.T) { + db := NewDatabase() + + data := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + }, + { + "label": "Age", + "fieldType": "number", + "required": false + }, + { + "label": "Is this a test?", + "fieldType": "boolean", + "required": false + }, + { + "label": "Favorite Food", + "fieldType": "[Pizza|Schnitzel|Burger]", + "required": true + }, + { + "label": "Favorite Foods", + "fieldType": "{Pizza|Schnitzel|Burger}", + "required": true + } + ]` + + formID := db.CreateForm("Test Form", "Test Description", "2020-01-01", "2020-01-02", data) + answers := `["Alex", 21, true, 0, [0, 1]]` + + db.SubmitForm(formID, answers) + + if len(db.Answers) != 1 { + t.Errorf("Expected 1 answer, got %d", len(db.Answers)) + } + + if db.Answers[0].FormID != formID { + t.Errorf("Expected form ID %s, got %s", formID, db.Answers[0].FormID) + } + + if db.Answers[0].Answers != answers { + t.Errorf("Expected answers %s, got %s", answers, db.Answers[0].Answers) + } +} diff --git a/examples/gno.land/p/demo/forms/validate.gno b/examples/gno.land/p/demo/forms/validate.gno new file mode 100644 index 00000000000..14ad58f6612 --- /dev/null +++ b/examples/gno.land/p/demo/forms/validate.gno @@ -0,0 +1,148 @@ +package forms + +import ( + "strings" + + "gno.land/p/demo/json" +) + +func ValidateBooleanField(node *json.Node, field Field) bool { + if node.IsBool() == false { + return false + } + + answer, err := node.GetBool() + if err != nil { + return false + } + + if field.Required == true && answer == false { + return false + } + + return true +} + +func ValidateStringField(node *json.Node, field Field) bool { + if node.IsString() == false { + return false + } + + answer, err := node.GetString() + if err != nil { + return false + } + + if field.Required == true && answer == "" { + return false + } + + return true +} + +func ValidateNumberField(node *json.Node, field Field) bool { + if node.IsNumber() == false { + return false + } + + answer, err := node.GetNumeric() + if err != nil { + return false + } + + if field.Required == true && answer == 0 { + return false + } + + return true +} + +func ValidateMultiChoiceField(node *json.Node, field Field) bool { + choices := strings.Split(field.FieldType[1:len(field.FieldType) - 1], "|") + + if node.IsArray() == false { + return false + } + + if field.Required == true && node.Size() == 0 { + return false + } + + if node.Size() > len(choices) { + return false + } + + for i := 0; i < node.Size(); i++ { + choiceNode, err := node.GetIndex(i) + if err != nil { + return false + } + + choiceIdx := choiceNode.MustNumeric() + if choiceIdx < 0 || int(choiceIdx) >= len(choices) { + return false + } + } + + return true +} + +func ValideChoiceField(node *json.Node, field Field) bool { + choices := strings.Split(field.FieldType[1:len(field.FieldType) - 1], "|") + + if node.IsNumber() == false { + return false + } + + choiceIdx := node.MustNumeric() + if choiceIdx < 0 || int(choiceIdx) >= len(choices) { + return false + } + + return true +} + +func ValidateAnswers(answers string, fields []Field) bool { + unmarshalled, err := json.Unmarshal([]byte(answers)) + if err != nil { + return false + } + + if len(fields) != unmarshalled.Size() { + return false + } + + for i, field := range fields { + answer, err := unmarshalled.GetIndex(i) + if err != nil { + return false + } + if field.FieldType == "boolean" { + if ValidateBooleanField(answer, field) == false { + return false + } + } + if field.FieldType == "string" { + if ValidateStringField(answer, field) == false { + return false + } + } + if field.FieldType == "number" { + if ValidateNumberField(answer, field) == false { + return false + } + } + if field.FieldType[0] == '{' && field.FieldType[len(field.FieldType) - 1] == '}' { + if ValidateMultiChoiceField(answer, field) == false { + return false + } + } + if field.FieldType[0] == '[' && field.FieldType[len(field.FieldType) - 1] == ']' { + if ValideChoiceField(answer, field) == false { + return false + } + } + } + + return true +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/validate_test.gno b/examples/gno.land/p/demo/forms/validate_test.gno new file mode 100644 index 00000000000..5024e86d9f9 --- /dev/null +++ b/examples/gno.land/p/demo/forms/validate_test.gno @@ -0,0 +1,110 @@ +package forms + +import ( + "testing" + + "gno.land/p/demo/testutils" +) + +func TestAnswerFormInvalidForm(t *testing.T) { + db := NewDatabase() + + data := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + }, + { + "label": "Age", + "fieldType": "number", + "required": false + }, + { + "label": "Is this a test?", + "fieldType": "boolean", + "required": false + }, + { + "label": "Favorite Food", + "fieldType": "[Pizza|Schnitzel|Burger]", + "required": true + }, + { + "label": "Favorite Foods", + "fieldType": "{Pizza|Schnitzel|Burger}", + "required": true + } + ]` + + formID := db.CreateForm("Test Form", "Test Description", "2020-01-01", "2020-01-02", data) + + tests := []struct { + name string + answer string + expectPanic bool + }{ + { + name: "correct", + answer: `["Alex", 21, true, 0, [0, 1]]`, + expectPanic: false, + }, + { + name: "invalid string", + answer: `[0, 21, true, 0, [0, 1]`, + expectPanic: true, + }, + { + name: "invalid number", + answer: `["Alex", "21", true, 0, [0, 1]]`, + expectPanic: true, + }, + { + name: "invalid boolean", + answer: `["Alex", 21, 1, 0, [0, 1]]`, + expectPanic: true, + }, + { + name: "invalid choice", + answer: `["Alex", 21, true, 10, [0, 1]]`, + expectPanic: true, + }, + { + name: "invalid multi-choice 1", + answer: `["Alex", 21, true, 0, [0, 1, 2, 3, 4, 5]]`, + expectPanic: true, + }, + { + name: "invalid multi-choice 2", + answer: `["Alex", 21, true, 0, [5]]`, + expectPanic: true, + }, + { + name: "invalid multi-choice 3", + answer: `["Alex", 21, true, 0, 0]`, + expectPanic: true, + }, + { + name: "required string", + answer: `["", true, 0, [0, 1]]`, + expectPanic: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if tt.expectPanic { + return + } + t.Errorf("%s panic occurred when not expected: %v", tt.name, r) + } else if tt.expectPanic { + t.Errorf("%s expected panic but didn't occur", tt.name) + } + }() + + db.SubmitForm(formID, tt.answer) + }) + } +} \ No newline at end of file diff --git a/examples/gno.land/r/demo/forms/forms.gno b/examples/gno.land/r/demo/forms/forms.gno new file mode 100644 index 00000000000..4daa7f21c0c --- /dev/null +++ b/examples/gno.land/r/demo/forms/forms.gno @@ -0,0 +1,126 @@ +package forms + +import ( + "gno.land/p/demo/forms" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/json" +) + +var db *forms.FormDatabase + +func init() { + db = forms.NewDatabase() +} + +func CreateForm(title string, description string, openAt string, closeAt string, data string) string { + return db.CreateForm(title, description, openAt, closeAt, data) +} + +func GetForms() string { + formsJson := json.ArrayNode("", []*json.Node{}) + for _, form := range db.Forms { + fieldsJson := json.ArrayNode("", []*json.Node{}) + for _, field := range form.Fields { + fieldJson := json.ObjectNode("", map[string]*json.Node{ + "label": json.StringNode("label", field.Label), + "fieldType": json.StringNode("fieldType", field.FieldType), + "required": json.BoolNode("required", field.Required), + }) + fieldsJson.AppendArray(fieldJson) + } + + formJson := json.ObjectNode("", map[string]*json.Node{ + "id": json.StringNode("id", form.ID), + "owner": json.StringNode("owner", form.Owner.String()), + "title": json.StringNode("title", form.Title), + "description": json.StringNode("description", form.Description), + "createdAt": json.StringNode("createdAt", form.CreatedAt), + "openAt": json.StringNode("openAt", form.OpenAt), + "closeAt": json.StringNode("closeAt", form.CloseAt), + "fields": fieldsJson, + }) + formsJson.AppendArray(formJson) + } + + encoded, err := json.Marshal(formsJson) + if err != nil { + panic(ufmt.Errorf("error: %v", err)) + } + + return string(encoded) +} + +func GetFormByID(id string) string { + form := db.GetForm(id) + if form == nil { + panic(ufmt.Errorf("Form not found: %s", id)) + } + + fieldsJson := json.ArrayNode("", []*json.Node{}) + for _, field := range form.Fields { + fieldJson := json.ObjectNode("", map[string]*json.Node{ + "label": json.StringNode("label", field.Label), + "fieldType": json.StringNode("fieldType", field.FieldType), + "required": json.BoolNode("required", field.Required), + }) + fieldsJson.AppendArray(fieldJson) + } + + encoded, _ := json.Marshal(fieldsJson) + ufmt.Println(string(encoded)) + ufmt.Println("DWADAWDWA") + + jsonRes := json.ObjectNode("", map[string]*json.Node{ + "id": json.StringNode("id", form.ID), + "owner": json.StringNode("owner", form.Owner.String()), + "title": json.StringNode("title", form.Title), + "description": json.StringNode("description", form.Description), + "createdAt": json.StringNode("createdAt", form.CreatedAt), + "openAt": json.StringNode("openAt", form.OpenAt), + "closeAt": json.StringNode("closeAt", form.CloseAt), + "fields": fieldsJson, + }) + + encoded, err := json.Marshal(jsonRes) + if err != nil { + panic(ufmt.Errorf("error: %v", err)) + } + + return string(encoded) +} + +func GetAnswer(formID string, authorID string) string { + form := db.GetForm(formID) + if form == nil { + panic(ufmt.Errorf("Form not found: %s", formID)) + } + + answer := db.GetAnswer(formID, authorID) + if answer == nil { + panic(ufmt.Errorf("Answer not found: %s", authorID)) + } + + return answer.Answers +} + +func SubmitForm(formID string, answers string) { + form := db.GetForm(formID) + if form == nil { + panic(ufmt.Errorf("Form not found: %s", formID)) + } + + db.SubmitForm(formID, answers) +} + +func Render(path string) string { + response := "Forms:\n\n" + for _, form := range db.Forms { + response += ufmt.Sprintf("- %s\n\n", GetFormByID(form.ID)) + } + response += "Answers:\n\n" + for _, answer := range db.Answers { + response += ufmt.Sprintf("- Form ID: %s\nAuthor: %s\nSubmitted At: %s\n>Answers: %s\n\n", answer.FormID, answer.Author, answer.SubmittedAt, answer.Answers) + } + + return response +} \ No newline at end of file diff --git a/examples/gno.land/r/demo/forms/gno.mod b/examples/gno.land/r/demo/forms/gno.mod new file mode 100644 index 00000000000..09755e9cd03 --- /dev/null +++ b/examples/gno.land/r/demo/forms/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/demo/forms + +require ( + gno.land/p/demo/forms v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest +) \ No newline at end of file From a19037a9084ed05eebafbe48ebdf0c3d7d4dc454 Mon Sep 17 00:00:00 2001 From: agherasie Date: Tue, 16 Jul 2024 20:02:58 +0200 Subject: [PATCH 02/20] chore: fmt gno-forms --- examples/gno.land/p/demo/forms/create.gno | 36 ++++++------- .../gno.land/p/demo/forms/create_test.gno | 6 +-- examples/gno.land/p/demo/forms/forms.gno | 36 ++++++------- examples/gno.land/p/demo/forms/submit.gno | 14 +++--- .../gno.land/p/demo/forms/submit_test.gno | 4 +- examples/gno.land/p/demo/forms/validate.gno | 20 ++++---- .../gno.land/p/demo/forms/validate_test.gno | 44 ++++++++-------- examples/gno.land/r/demo/forms/forms.gno | 50 +++++++++---------- 8 files changed, 103 insertions(+), 107 deletions(-) diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index 746688144a1..e894ba85cfa 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -1,18 +1,18 @@ package forms -import ( - "time" +import ( "std" - + "time" + + "gno.land/p/demo/json" "gno.land/p/demo/ufmt" - "gno.land/p/demo/json" ) func (form *Form) AddField(label string, fieldType string, required bool) { field := Field{ - Label: label, + Label: label, FieldType: fieldType, - Required: required, + Required: required, } form.Fields = append(form.Fields, field) @@ -31,21 +31,21 @@ func (db *FormDatabase) CreateForm(title string, description string, openAt stri id := db.IDCounter.Next().String() form := &Form{ - ID: id, - Owner: std.PrevRealm().Addr(), - Title: title, + ID: id, + Owner: std.PrevRealm().Addr(), + Title: title, Description: description, - CreatedAt: time.Now().String(), - OpenAt: openAt, - CloseAt: closeAt, + CreatedAt: time.Now().String(), + OpenAt: openAt, + CloseAt: closeAt, } db.Forms = append(db.Forms, form) - node, err := json.Unmarshal([]byte(data)) - if err != nil { - ufmt.Errorf("error: %v", err) - } + node, err := json.Unmarshal([]byte(data)) + if err != nil { + ufmt.Errorf("error: %v", err) + } for i := 0; i < node.Size(); i++ { field, err := node.GetIndex(i) @@ -65,5 +65,5 @@ func (db *FormDatabase) CreateForm(title string, description string, openAt stri form.AddField(label, fieldType, required) } - return id -} \ No newline at end of file + return id +} diff --git a/examples/gno.land/p/demo/forms/create_test.gno b/examples/gno.land/p/demo/forms/create_test.gno index b26f60d65f1..2ede4a54592 100644 --- a/examples/gno.land/p/demo/forms/create_test.gno +++ b/examples/gno.land/p/demo/forms/create_test.gno @@ -1,8 +1,8 @@ package forms import ( - "testing" "std" + "testing" "gno.land/p/demo/testutils" ) @@ -44,7 +44,7 @@ func TestCreateForm(t *testing.T) { ]` db := NewDatabase() id := db.CreateForm(title, description, openAt, closeAt, data) - if id == "" { + if id == "" { t.Error("Form ID is empty") } form := db.GetForm(id) @@ -78,4 +78,4 @@ func TestCreateForm(t *testing.T) { if form.Fields[1].FieldType != "number" { t.Error("Field 1 type is not correct") } -} \ No newline at end of file +} diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index 5e64aedc766..bf722ac250a 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -1,7 +1,7 @@ package forms import ( - "std" + "std" "gno.land/p/demo/seqid" ) @@ -9,12 +9,12 @@ import ( type Field struct { Label string - /* - string: "string"; - number: "number"; - boolean: "boolean"; - choice: "['Pizza', 'Schnitzel', 'Burger']"; - multi-choice: "{'Pizza', 'Schnitzel', 'Burger'}"; + /* + string: "string"; + number: "number"; + boolean: "boolean"; + choice: "['Pizza', 'Schnitzel', 'Burger']"; + multi-choice: "{'Pizza', 'Schnitzel', 'Burger'}"; */ FieldType string @@ -22,23 +22,23 @@ type Field struct { } type Form struct { - ID string + ID string Owner std.Address - - Title string + + Title string Description string - Fields []Field - + Fields []Field + CreatedAt string - OpenAt string - CloseAt string + OpenAt string + CloseAt string } type Submission struct { FormID string Author std.Address - + /* ["Alex", 21, true, 0, [0, 1]] */ Answers string // json @@ -47,7 +47,7 @@ type Submission struct { type FormDatabase struct { Forms []*Form - + Answers []*Submission IDCounter seqid.ID @@ -55,7 +55,7 @@ type FormDatabase struct { func NewDatabase() *FormDatabase { return &FormDatabase{ - Forms: make([]*Form, 0), + Forms: make([]*Form, 0), Answers: make([]*Submission, 0), } -} \ No newline at end of file +} diff --git a/examples/gno.land/p/demo/forms/submit.gno b/examples/gno.land/p/demo/forms/submit.gno index 1395cb59682..5e20d27e5af 100644 --- a/examples/gno.land/p/demo/forms/submit.gno +++ b/examples/gno.land/p/demo/forms/submit.gno @@ -1,9 +1,9 @@ package forms -import ( - "time" +import ( "std" - + "time" + "gno.land/p/demo/ufmt" ) @@ -18,9 +18,9 @@ func (db *FormDatabase) SubmitForm(formID string, answers string) { } answer := &Submission{ - FormID: formID, - Answers: answers, - Author: std.PrevRealm().Addr(), + FormID: formID, + Answers: answers, + Author: std.PrevRealm().Addr(), SubmittedAt: time.Now().String(), } @@ -34,4 +34,4 @@ func (db *FormDatabase) GetAnswer(formID string, authorID string) *Submission { } } return nil -} \ No newline at end of file +} diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index 20365bcf734..cbf62bf5b6b 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -2,8 +2,6 @@ package forms import ( "testing" - - "gno.land/p/demo/testutils" ) func TestAnswerForm(t *testing.T) { @@ -53,4 +51,4 @@ func TestAnswerForm(t *testing.T) { if db.Answers[0].Answers != answers { t.Errorf("Expected answers %s, got %s", answers, db.Answers[0].Answers) } -} +} diff --git a/examples/gno.land/p/demo/forms/validate.gno b/examples/gno.land/p/demo/forms/validate.gno index 14ad58f6612..2168faa74a4 100644 --- a/examples/gno.land/p/demo/forms/validate.gno +++ b/examples/gno.land/p/demo/forms/validate.gno @@ -2,8 +2,8 @@ package forms import ( "strings" - - "gno.land/p/demo/json" + + "gno.land/p/demo/json" ) func ValidateBooleanField(node *json.Node, field Field) bool { @@ -58,8 +58,8 @@ func ValidateNumberField(node *json.Node, field Field) bool { } func ValidateMultiChoiceField(node *json.Node, field Field) bool { - choices := strings.Split(field.FieldType[1:len(field.FieldType) - 1], "|") - + choices := strings.Split(field.FieldType[1:len(field.FieldType)-1], "|") + if node.IsArray() == false { return false } @@ -77,7 +77,7 @@ func ValidateMultiChoiceField(node *json.Node, field Field) bool { if err != nil { return false } - + choiceIdx := choiceNode.MustNumeric() if choiceIdx < 0 || int(choiceIdx) >= len(choices) { return false @@ -88,8 +88,8 @@ func ValidateMultiChoiceField(node *json.Node, field Field) bool { } func ValideChoiceField(node *json.Node, field Field) bool { - choices := strings.Split(field.FieldType[1:len(field.FieldType) - 1], "|") - + choices := strings.Split(field.FieldType[1:len(field.FieldType)-1], "|") + if node.IsNumber() == false { return false } @@ -132,12 +132,12 @@ func ValidateAnswers(answers string, fields []Field) bool { return false } } - if field.FieldType[0] == '{' && field.FieldType[len(field.FieldType) - 1] == '}' { + if field.FieldType[0] == '{' && field.FieldType[len(field.FieldType)-1] == '}' { if ValidateMultiChoiceField(answer, field) == false { return false } } - if field.FieldType[0] == '[' && field.FieldType[len(field.FieldType) - 1] == ']' { + if field.FieldType[0] == '[' && field.FieldType[len(field.FieldType)-1] == ']' { if ValideChoiceField(answer, field) == false { return false } @@ -145,4 +145,4 @@ func ValidateAnswers(answers string, fields []Field) bool { } return true -} \ No newline at end of file +} diff --git a/examples/gno.land/p/demo/forms/validate_test.gno b/examples/gno.land/p/demo/forms/validate_test.gno index 5024e86d9f9..3b624e3ddd5 100644 --- a/examples/gno.land/p/demo/forms/validate_test.gno +++ b/examples/gno.land/p/demo/forms/validate_test.gno @@ -2,13 +2,11 @@ package forms import ( "testing" - - "gno.land/p/demo/testutils" ) func TestAnswerFormInvalidForm(t *testing.T) { db := NewDatabase() - + data := `[ { "label": "Name", @@ -38,55 +36,55 @@ func TestAnswerFormInvalidForm(t *testing.T) { ]` formID := db.CreateForm("Test Form", "Test Description", "2020-01-01", "2020-01-02", data) - + tests := []struct { name string answer string expectPanic bool }{ { - name: "correct", - answer: `["Alex", 21, true, 0, [0, 1]]`, + name: "correct", + answer: `["Alex", 21, true, 0, [0, 1]]`, expectPanic: false, }, { - name: "invalid string", - answer: `[0, 21, true, 0, [0, 1]`, + name: "invalid string", + answer: `[0, 21, true, 0, [0, 1]`, expectPanic: true, }, { - name: "invalid number", - answer: `["Alex", "21", true, 0, [0, 1]]`, + name: "invalid number", + answer: `["Alex", "21", true, 0, [0, 1]]`, expectPanic: true, }, { - name: "invalid boolean", - answer: `["Alex", 21, 1, 0, [0, 1]]`, + name: "invalid boolean", + answer: `["Alex", 21, 1, 0, [0, 1]]`, expectPanic: true, }, { - name: "invalid choice", - answer: `["Alex", 21, true, 10, [0, 1]]`, + name: "invalid choice", + answer: `["Alex", 21, true, 10, [0, 1]]`, expectPanic: true, }, { - name: "invalid multi-choice 1", - answer: `["Alex", 21, true, 0, [0, 1, 2, 3, 4, 5]]`, + name: "invalid multi-choice 1", + answer: `["Alex", 21, true, 0, [0, 1, 2, 3, 4, 5]]`, expectPanic: true, }, { - name: "invalid multi-choice 2", - answer: `["Alex", 21, true, 0, [5]]`, + name: "invalid multi-choice 2", + answer: `["Alex", 21, true, 0, [5]]`, expectPanic: true, }, { - name: "invalid multi-choice 3", - answer: `["Alex", 21, true, 0, 0]`, + name: "invalid multi-choice 3", + answer: `["Alex", 21, true, 0, 0]`, expectPanic: true, }, { - name: "required string", - answer: `["", true, 0, [0, 1]]`, + name: "required string", + answer: `["", true, 0, [0, 1]]`, expectPanic: true, }, } @@ -107,4 +105,4 @@ func TestAnswerFormInvalidForm(t *testing.T) { db.SubmitForm(formID, tt.answer) }) } -} \ No newline at end of file +} diff --git a/examples/gno.land/r/demo/forms/forms.gno b/examples/gno.land/r/demo/forms/forms.gno index 4daa7f21c0c..64fdba37bea 100644 --- a/examples/gno.land/r/demo/forms/forms.gno +++ b/examples/gno.land/r/demo/forms/forms.gno @@ -1,9 +1,9 @@ package forms import ( - "gno.land/p/demo/forms" - "gno.land/p/demo/ufmt" + "gno.land/p/demo/forms" "gno.land/p/demo/json" + "gno.land/p/demo/ufmt" ) var db *forms.FormDatabase @@ -22,22 +22,22 @@ func GetForms() string { fieldsJson := json.ArrayNode("", []*json.Node{}) for _, field := range form.Fields { fieldJson := json.ObjectNode("", map[string]*json.Node{ - "label": json.StringNode("label", field.Label), + "label": json.StringNode("label", field.Label), "fieldType": json.StringNode("fieldType", field.FieldType), - "required": json.BoolNode("required", field.Required), + "required": json.BoolNode("required", field.Required), }) fieldsJson.AppendArray(fieldJson) - } + } formJson := json.ObjectNode("", map[string]*json.Node{ - "id": json.StringNode("id", form.ID), - "owner": json.StringNode("owner", form.Owner.String()), - "title": json.StringNode("title", form.Title), + "id": json.StringNode("id", form.ID), + "owner": json.StringNode("owner", form.Owner.String()), + "title": json.StringNode("title", form.Title), "description": json.StringNode("description", form.Description), - "createdAt": json.StringNode("createdAt", form.CreatedAt), - "openAt": json.StringNode("openAt", form.OpenAt), - "closeAt": json.StringNode("closeAt", form.CloseAt), - "fields": fieldsJson, + "createdAt": json.StringNode("createdAt", form.CreatedAt), + "openAt": json.StringNode("openAt", form.OpenAt), + "closeAt": json.StringNode("closeAt", form.CloseAt), + "fields": fieldsJson, }) formsJson.AppendArray(formJson) } @@ -59,9 +59,9 @@ func GetFormByID(id string) string { fieldsJson := json.ArrayNode("", []*json.Node{}) for _, field := range form.Fields { fieldJson := json.ObjectNode("", map[string]*json.Node{ - "label": json.StringNode("label", field.Label), + "label": json.StringNode("label", field.Label), "fieldType": json.StringNode("fieldType", field.FieldType), - "required": json.BoolNode("required", field.Required), + "required": json.BoolNode("required", field.Required), }) fieldsJson.AppendArray(fieldJson) } @@ -71,15 +71,15 @@ func GetFormByID(id string) string { ufmt.Println("DWADAWDWA") jsonRes := json.ObjectNode("", map[string]*json.Node{ - "id": json.StringNode("id", form.ID), - "owner": json.StringNode("owner", form.Owner.String()), - "title": json.StringNode("title", form.Title), + "id": json.StringNode("id", form.ID), + "owner": json.StringNode("owner", form.Owner.String()), + "title": json.StringNode("title", form.Title), "description": json.StringNode("description", form.Description), - "createdAt": json.StringNode("createdAt", form.CreatedAt), - "openAt": json.StringNode("openAt", form.OpenAt), - "closeAt": json.StringNode("closeAt", form.CloseAt), - "fields": fieldsJson, - }) + "createdAt": json.StringNode("createdAt", form.CreatedAt), + "openAt": json.StringNode("openAt", form.OpenAt), + "closeAt": json.StringNode("closeAt", form.CloseAt), + "fields": fieldsJson, + }) encoded, err := json.Marshal(jsonRes) if err != nil { @@ -108,7 +108,7 @@ func SubmitForm(formID string, answers string) { if form == nil { panic(ufmt.Errorf("Form not found: %s", formID)) } - + db.SubmitForm(formID, answers) } @@ -119,8 +119,8 @@ func Render(path string) string { } response += "Answers:\n\n" for _, answer := range db.Answers { - response += ufmt.Sprintf("- Form ID: %s\nAuthor: %s\nSubmitted At: %s\n>Answers: %s\n\n", answer.FormID, answer.Author, answer.SubmittedAt, answer.Answers) + response += ufmt.Sprintf("- Form ID: %s\nAuthor: %s\nSubmitted At: %s\n>Answers: %s\n\n", answer.FormID, answer.Author, answer.SubmittedAt, answer.Answers) } return response -} \ No newline at end of file +} From 34c23eb4bd2e2ffcf8d6453a5d751325490a88a3 Mon Sep 17 00:00:00 2001 From: agherasie Date: Tue, 16 Jul 2024 20:17:06 +0200 Subject: [PATCH 03/20] feat(examples): gno-forms readme and comments --- examples/gno.land/p/demo/forms/README.md | 23 +++++++++++++++++++++++ examples/gno.land/p/demo/forms/create.gno | 1 + 2 files changed, 24 insertions(+) create mode 100644 examples/gno.land/p/demo/forms/README.md diff --git a/examples/gno.land/p/demo/forms/README.md b/examples/gno.land/p/demo/forms/README.md new file mode 100644 index 00000000000..f1bab87b34a --- /dev/null +++ b/examples/gno.land/p/demo/forms/README.md @@ -0,0 +1,23 @@ +# Gno forms + +gno-forms is a package which demonstrates a form editing and sharing application in gno + +## Features +- **Form Creation**: Create new forms with specified titles, descriptions, and fields. +- **Form Submission**: Submit answers to forms. +- **Form Retrieval**: Retrieve existing forms and their submissions. + +## Field Types +The system supports the following field types: + +type|example +-|- +string|`{"label": "Name", "fieldType": "string", "required": true}` +number|`{"label": "Age", "fieldType": "number", "required": true}` +boolean|`{"label": "Is Student?", "fieldType": "boolean", "required": false}` +choice|`{"label": "Favorite Food", "fieldType": "['Pizza', 'Schnitzel', 'Burger']", "required": true}` +multi-choice|`{"label": "Hobbies", "fieldType": "{'Reading', 'Swimming', 'Gaming'}", "required": false}` + +## Web-app + +The external repo where the initial development took place and where you can find the frontend is [here](https://github.com/agherasie/gno-forms). \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index e894ba85cfa..d9015f63424 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -47,6 +47,7 @@ func (db *FormDatabase) CreateForm(title string, description string, openAt stri ufmt.Errorf("error: %v", err) } + // Parsing the json submission to create the gno data structures for i := 0; i < node.Size(); i++ { field, err := node.GetIndex(i) From 1ac3eeb640f8f121558f1fa678d5721b9d26d303 Mon Sep 17 00:00:00 2001 From: agherasie Date: Thu, 18 Jul 2024 19:58:12 +0300 Subject: [PATCH 04/20] feat(examples): r/forms date restrictions --- examples/gno.land/p/demo/forms/README.md | 1 + examples/gno.land/p/demo/forms/create.gno | 77 +++++++++++------- examples/gno.land/p/demo/forms/forms.gno | 78 ++++++++++++++++++- examples/gno.land/p/demo/forms/submit.gno | 29 ++++--- .../gno.land/p/demo/forms/submit_test.gno | 43 +++++++++- examples/gno.land/p/demo/forms/validate.gno | 27 +++---- .../gno.land/p/demo/forms/validate_test.gno | 37 ++++++++- examples/gno.land/r/demo/forms/forms.gno | 35 ++++++--- 8 files changed, 257 insertions(+), 70 deletions(-) diff --git a/examples/gno.land/p/demo/forms/README.md b/examples/gno.land/p/demo/forms/README.md index f1bab87b34a..d41c6b55af8 100644 --- a/examples/gno.land/p/demo/forms/README.md +++ b/examples/gno.land/p/demo/forms/README.md @@ -6,6 +6,7 @@ gno-forms is a package which demonstrates a form editing and sharing application - **Form Creation**: Create new forms with specified titles, descriptions, and fields. - **Form Submission**: Submit answers to forms. - **Form Retrieval**: Retrieve existing forms and their submissions. +- **Form Deadline**: Set a precise time range during which a form can be interacted with. ## Field Types The system supports the following field types: diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index d9015f63424..7320139d5c2 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -8,49 +8,56 @@ import ( "gno.land/p/demo/ufmt" ) -func (form *Form) AddField(label string, fieldType string, required bool) { - field := Field{ +func CreateField(label string, fieldType string, required bool) Field { + return Field{ Label: label, FieldType: fieldType, Required: required, } - - form.Fields = append(form.Fields, field) } -func (db *FormDatabase) GetForm(id string) *Form { - for _, form := range db.Forms { - if form.ID == id { - return form +func ParseDates(openAt string, closeAt string) (*time.Time, *time.Time) { + var openAtTime, closeAtTime *time.Time + + dateFormat := "2006-01-02T15:04:05Z" + + // Parse openAt if it's not empty + if openAt != "" { + res, err := time.Parse(dateFormat, openAt) + if err != nil { + panic(ufmt.Errorf("invalid date: %v", openAt)) } + openAtTime = &res } - return nil -} - -func (db *FormDatabase) CreateForm(title string, description string, openAt string, closeAt string, data string) string { - id := db.IDCounter.Next().String() - form := &Form{ - ID: id, - Owner: std.PrevRealm().Addr(), - Title: title, - Description: description, - CreatedAt: time.Now().String(), - OpenAt: openAt, - CloseAt: closeAt, + // Parse closeAt if it's not empty + if closeAt != "" { + res, err := time.Parse(dateFormat, closeAt) + if err != nil { + panic(ufmt.Errorf("invalid date: %v", closeAt)) + } + closeAtTime = &res } - db.Forms = append(db.Forms, form) + return openAtTime, closeAtTime +} + +func (db *FormDatabase) CreateForm(title string, description string, openAt string, closeAt string, data string) string { + // Parsing dates + openAtTime, closeAtTime := ParseDates(openAt, closeAt) + // Parsing the json submission node, err := json.Unmarshal([]byte(data)) if err != nil { - ufmt.Errorf("error: %v", err) + ufmt.Errorf("invalid json: %v", err) } + fieldsCount := node.Size() + fields := make([]Field, fieldsCount) + // Parsing the json submission to create the gno data structures - for i := 0; i < node.Size(); i++ { + for i := 0; i < fieldsCount; i++ { field, err := node.GetIndex(i) - if err != nil { ufmt.Errorf("error: %v", err) } @@ -63,8 +70,26 @@ func (db *FormDatabase) CreateForm(title string, description string, openAt stri fieldType, _ := fieldTypeNode.GetString() required, _ := requiredNode.GetBool() - form.AddField(label, fieldType, required) + fields[i] = CreateField(label, fieldType, required) } + // Generating the form ID + id := db.IDCounter.Next().String() + + // Creating the form + form := Form{ + ID: id, + Owner: std.PrevRealm().Addr(), + Title: title, + Description: description, + CreatedAt: time.Now(), + openAt: openAtTime, + closeAt: closeAtTime, + Fields: fields, + } + + // Adding the form to the database + db.Forms = append(db.Forms, &form) + return id } diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index bf722ac250a..cb79df405ff 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -2,6 +2,8 @@ package forms import ( "std" + "time" + "errors" "gno.land/p/demo/seqid" ) @@ -29,9 +31,9 @@ type Form struct { Description string Fields []Field - CreatedAt string - OpenAt string - CloseAt string + CreatedAt time.Time + openAt *time.Time + closeAt *time.Time } type Submission struct { @@ -42,7 +44,7 @@ type Submission struct { /* ["Alex", 21, true, 0, [0, 1]] */ Answers string // json - SubmittedAt string + SubmittedAt time.Time } type FormDatabase struct { @@ -59,3 +61,71 @@ func NewDatabase() *FormDatabase { Answers: make([]*Submission, 0), } } + +func (form *Form) IsOpen() bool { + openAt, errOpen := form.OpenAt() + closedAt, errClose := form.CloseAt() + + noOpenDate := errOpen != nil + noCloseDate := errClose != nil + + if noOpenDate && noCloseDate { + return true + } + + if noOpenDate && !noCloseDate { + return time.Now().Before(closedAt) + } + + if !noOpenDate && noCloseDate { + return time.Now().After(openAt) + } + + return time.Now().After(openAt) && time.Now().Before(closedAt) +} + +func (form *Form) OpenAt() (time.Time, error) { + if form.openAt == nil { + return time.Time{}, errors.New("Form has no open date") + } + + return *form.openAt, nil +} + +func (form *Form) CloseAt() (time.Time, error) { + if form.closeAt == nil { + return time.Time{}, errors.New("Form has no close date") + } + + return *form.closeAt, nil +} + +func (db *FormDatabase) GetForm(id string) *Form { + for _, form := range db.Forms { + if form.ID == id { + return form + } + } + return nil +} + +func (db *FormDatabase) GetAnswer(formID string, author std.Address) *Submission { + for _, answer := range db.Answers { + if answer.FormID == formID && answer.Author.String() == author.String() { + return answer + } + } + return nil +} + +func (db *FormDatabase) GetSubmissionsByFormID(formID string) []*Submission { + submissions := make([]*Submission, 0) + + for _, answer := range db.Answers { + if answer.FormID == formID { + submissions = append(submissions, answer) + } + } + + return submissions +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/submit.gno b/examples/gno.land/p/demo/forms/submit.gno index 5e20d27e5af..d7c0921d18f 100644 --- a/examples/gno.land/p/demo/forms/submit.gno +++ b/examples/gno.land/p/demo/forms/submit.gno @@ -8,30 +8,35 @@ import ( ) func (db *FormDatabase) SubmitForm(formID string, answers string) { + // Check if form exists form := db.GetForm(formID) if form == nil { panic(ufmt.Errorf("Form not found: %s", formID)) } + // Check if form was already submitted by this user + previousAnswer := db.GetAnswer(formID, std.PrevRealm().Addr()) + if previousAnswer != nil { + panic("You already submitted this form") + } + + // Check time restrictions + if !form.IsOpen() { + panic("Form is closed") + } + + // Check if answers are formatted correctly if ValidateAnswers(answers, form.Fields) == false { panic("Invalid answers") } - answer := &Submission{ + // Save answers + answer := Submission{ FormID: formID, Answers: answers, Author: std.PrevRealm().Addr(), - SubmittedAt: time.Now().String(), + SubmittedAt: time.Now(), } - - db.Answers = append(db.Answers, answer) + db.Answers = append(db.Answers, &answer) } -func (db *FormDatabase) GetAnswer(formID string, authorID string) *Submission { - for _, answer := range db.Answers { - if answer.FormID == formID && answer.Author.String() == authorID { - return answer - } - } - return nil -} diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index cbf62bf5b6b..bfeaaddb4a2 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -1,6 +1,8 @@ package forms import ( + "time" + "testing" ) @@ -35,7 +37,7 @@ func TestAnswerForm(t *testing.T) { } ]` - formID := db.CreateForm("Test Form", "Test Description", "2020-01-01", "2020-01-02", data) + formID := db.CreateForm("Test Form", "Test Description", "", "", data) answers := `["Alex", 21, true, 0, [0, 1]]` db.SubmitForm(formID, answers) @@ -52,3 +54,42 @@ func TestAnswerForm(t *testing.T) { t.Errorf("Expected answers %s, got %s", answers, db.Answers[0].Answers) } } + +func shouldPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("should have panic") + } + }() + f() +} + +func TestAnswerFormDates(t *testing.T) { + db := NewDatabase() + + now := time.Now() + tomorrow := now.AddDate(0, 0, 1).Format("2006-01-02T15:04:05Z") + yesterday := now.AddDate(0, 0, -1).Format("2006-01-02T15:04:05Z") + + data := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + } + ]` + answers := `["Test"]` + + shouldPanic(t, func() { + formID := db.CreateForm("Test Form", "Test Description", tomorrow, "", data) + db.SubmitForm(formID, answers) + }) + + shouldPanic(t, func() { + formID := db.CreateForm("Test Form", "Test Description", "", yesterday, data) + db.SubmitForm(formID, answers) + }) + + formID := db.CreateForm("Test Form", "Test Description", yesterday, tomorrow, data) + db.SubmitForm(formID, answers) +} diff --git a/examples/gno.land/p/demo/forms/validate.gno b/examples/gno.land/p/demo/forms/validate.gno index 2168faa74a4..8fe542f235a 100644 --- a/examples/gno.land/p/demo/forms/validate.gno +++ b/examples/gno.land/p/demo/forms/validate.gno @@ -16,6 +16,7 @@ func ValidateBooleanField(node *json.Node, field Field) bool { return false } + // If the field is required, checkbox must be checked if field.Required == true && answer == false { return false } @@ -33,6 +34,7 @@ func ValidateStringField(node *json.Node, field Field) bool { return false } + // If the field is required, the answer must not be empty if field.Required == true && answer == "" { return false } @@ -45,15 +47,11 @@ func ValidateNumberField(node *json.Node, field Field) bool { return false } - answer, err := node.GetNumeric() + _, err := node.GetNumeric() if err != nil { return false } - if field.Required == true && answer == 0 { - return false - } - return true } @@ -117,30 +115,33 @@ func ValidateAnswers(answers string, fields []Field) bool { if err != nil { return false } + + if answer.IsNull() && !field.Required { + return true + } + if field.FieldType == "boolean" { if ValidateBooleanField(answer, field) == false { return false } - } - if field.FieldType == "string" { + } else if field.FieldType == "string" { if ValidateStringField(answer, field) == false { return false } - } - if field.FieldType == "number" { + } else if field.FieldType == "number" { if ValidateNumberField(answer, field) == false { return false } - } - if field.FieldType[0] == '{' && field.FieldType[len(field.FieldType)-1] == '}' { + } else if field.FieldType[0] == '{' && field.FieldType[len(field.FieldType)-1] == '}' { if ValidateMultiChoiceField(answer, field) == false { return false } - } - if field.FieldType[0] == '[' && field.FieldType[len(field.FieldType)-1] == ']' { + } else if field.FieldType[0] == '[' && field.FieldType[len(field.FieldType)-1] == ']' { if ValideChoiceField(answer, field) == false { return false } + } else { + return false } } diff --git a/examples/gno.land/p/demo/forms/validate_test.gno b/examples/gno.land/p/demo/forms/validate_test.gno index 3b624e3ddd5..a99172feb12 100644 --- a/examples/gno.land/p/demo/forms/validate_test.gno +++ b/examples/gno.land/p/demo/forms/validate_test.gno @@ -7,7 +7,7 @@ import ( func TestAnswerFormInvalidForm(t *testing.T) { db := NewDatabase() - data := `[ + dataAllTypes := `[ { "label": "Name", "fieldType": "string", @@ -34,63 +34,92 @@ func TestAnswerFormInvalidForm(t *testing.T) { "required": true } ]` - - formID := db.CreateForm("Test Form", "Test Description", "2020-01-01", "2020-01-02", data) + dataOneRequiredText := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + } + ]` tests := []struct { name string answer string expectPanic bool + data string }{ { name: "correct", answer: `["Alex", 21, true, 0, [0, 1]]`, expectPanic: false, + data: dataAllTypes, }, { name: "invalid string", answer: `[0, 21, true, 0, [0, 1]`, expectPanic: true, + data: dataAllTypes, }, { name: "invalid number", answer: `["Alex", "21", true, 0, [0, 1]]`, expectPanic: true, + data: dataAllTypes, }, { name: "invalid boolean", answer: `["Alex", 21, 1, 0, [0, 1]]`, expectPanic: true, + data: dataAllTypes, }, { name: "invalid choice", answer: `["Alex", 21, true, 10, [0, 1]]`, expectPanic: true, + data: dataAllTypes, }, { name: "invalid multi-choice 1", answer: `["Alex", 21, true, 0, [0, 1, 2, 3, 4, 5]]`, expectPanic: true, + data: dataAllTypes, }, { name: "invalid multi-choice 2", answer: `["Alex", 21, true, 0, [5]]`, expectPanic: true, + data: dataAllTypes, }, { name: "invalid multi-choice 3", answer: `["Alex", 21, true, 0, 0]`, expectPanic: true, + data: dataAllTypes, }, { name: "required string", - answer: `["", true, 0, [0, 1]]`, + answer: `["", 21, true, 0, [0, 1]]`, expectPanic: true, + data: dataAllTypes, + }, + { + name: "unrequired number", + answer: `["Alex", null, true, 0, [0, 1]]`, + expectPanic: false, + data: dataAllTypes, + }, + { + name: "correct one field", + answer: `["Alex"]`, + expectPanic: false, + data: dataOneRequiredText, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + formID := db.CreateForm("Test Form", "Test Description", "", "", tt.data) + defer func() { if r := recover(); r != nil { if tt.expectPanic { diff --git a/examples/gno.land/r/demo/forms/forms.gno b/examples/gno.land/r/demo/forms/forms.gno index 64fdba37bea..ad54cf88d8c 100644 --- a/examples/gno.land/r/demo/forms/forms.gno +++ b/examples/gno.land/r/demo/forms/forms.gno @@ -1,6 +1,9 @@ package forms import ( + "std" + "strings" + "gno.land/p/demo/forms" "gno.land/p/demo/json" "gno.land/p/demo/ufmt" @@ -34,9 +37,7 @@ func GetForms() string { "owner": json.StringNode("owner", form.Owner.String()), "title": json.StringNode("title", form.Title), "description": json.StringNode("description", form.Description), - "createdAt": json.StringNode("createdAt", form.CreatedAt), - "openAt": json.StringNode("openAt", form.OpenAt), - "closeAt": json.StringNode("closeAt", form.CloseAt), + "createdAt": json.StringNode("createdAt", form.CreatedAt.String()), "fields": fieldsJson, }) formsJson.AppendArray(formJson) @@ -66,21 +67,35 @@ func GetFormByID(id string) string { fieldsJson.AppendArray(fieldJson) } - encoded, _ := json.Marshal(fieldsJson) - ufmt.Println(string(encoded)) - ufmt.Println("DWADAWDWA") + formSubmissions := db.GetSubmissionsByFormID(form.ID) + submissionsJson := json.ObjectNode("", map[string]*json.Node{}) + for _, submission := range formSubmissions { + submissionJson := json.ObjectNode("", map[string]*json.Node{ + "submittedAt": json.StringNode("submittedAt", submission.SubmittedAt.Format("2006-01-02 15:04:05")), + "answers": json.StringNode("answers", strings.ReplaceAll(submission.Answers, "\"", "'")), + }) + submissionsJson.AppendObject(submission.Author.String(), submissionJson) + } jsonRes := json.ObjectNode("", map[string]*json.Node{ "id": json.StringNode("id", form.ID), "owner": json.StringNode("owner", form.Owner.String()), "title": json.StringNode("title", form.Title), "description": json.StringNode("description", form.Description), - "createdAt": json.StringNode("createdAt", form.CreatedAt), - "openAt": json.StringNode("openAt", form.OpenAt), - "closeAt": json.StringNode("closeAt", form.CloseAt), + "createdAt": json.StringNode("createdAt", form.CreatedAt.Format("2006-01-02 15:04:05")), + "submissions": submissionsJson, "fields": fieldsJson, }) + openAt, err := form.OpenAt() + if err == nil { + jsonRes.AppendObject("openAt", json.StringNode("openAt", openAt.Format("2006-01-02 15:04:05"))) + } + closeAt, err := form.CloseAt() + if err == nil { + jsonRes.AppendObject("closeAt", json.StringNode("closeAt", closeAt.Format("2006-01-02 15:04:05"))) + } + encoded, err := json.Marshal(jsonRes) if err != nil { panic(ufmt.Errorf("error: %v", err)) @@ -95,7 +110,7 @@ func GetAnswer(formID string, authorID string) string { panic(ufmt.Errorf("Form not found: %s", formID)) } - answer := db.GetAnswer(formID, authorID) + answer := db.GetAnswer(formID, std.Address(authorID)) if answer == nil { panic(ufmt.Errorf("Answer not found: %s", authorID)) } From 332d8565879ccb8bc3d956b756bc92aa36435770 Mon Sep 17 00:00:00 2001 From: agherasie Date: Thu, 18 Jul 2024 20:13:08 +0300 Subject: [PATCH 05/20] chore(examples): fmt --- examples/gno.land/p/demo/forms/create.gno | 2 +- examples/gno.land/p/demo/forms/forms.gno | 4 +-- examples/gno.land/p/demo/forms/submit.gno | 1 - .../gno.land/p/demo/forms/submit_test.gno | 14 +++++----- examples/gno.land/p/demo/forms/validate.gno | 2 +- .../gno.land/p/demo/forms/validate_test.gno | 28 +++++++++---------- 6 files changed, 25 insertions(+), 26 deletions(-) diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index 7320139d5c2..c1da4d108fc 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -54,7 +54,7 @@ func (db *FormDatabase) CreateForm(title string, description string, openAt stri fieldsCount := node.Size() fields := make([]Field, fieldsCount) - + // Parsing the json submission to create the gno data structures for i := 0; i < fieldsCount; i++ { field, err := node.GetIndex(i) diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index cb79df405ff..65975ad59b4 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -1,9 +1,9 @@ package forms import ( + "errors" "std" "time" - "errors" "gno.land/p/demo/seqid" ) @@ -128,4 +128,4 @@ func (db *FormDatabase) GetSubmissionsByFormID(formID string) []*Submission { } return submissions -} \ No newline at end of file +} diff --git a/examples/gno.land/p/demo/forms/submit.gno b/examples/gno.land/p/demo/forms/submit.gno index d7c0921d18f..4aa4d6fbd28 100644 --- a/examples/gno.land/p/demo/forms/submit.gno +++ b/examples/gno.land/p/demo/forms/submit.gno @@ -39,4 +39,3 @@ func (db *FormDatabase) SubmitForm(formID string, answers string) { } db.Answers = append(db.Answers, &answer) } - diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index bfeaaddb4a2..0d57ccfd71d 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -56,12 +56,12 @@ func TestAnswerForm(t *testing.T) { } func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("should have panic") - } - }() - f() + defer func() { + if r := recover(); r == nil { + t.Errorf("should have panic") + } + }() + f() } func TestAnswerFormDates(t *testing.T) { @@ -79,7 +79,7 @@ func TestAnswerFormDates(t *testing.T) { } ]` answers := `["Test"]` - + shouldPanic(t, func() { formID := db.CreateForm("Test Form", "Test Description", tomorrow, "", data) db.SubmitForm(formID, answers) diff --git a/examples/gno.land/p/demo/forms/validate.gno b/examples/gno.land/p/demo/forms/validate.gno index 8fe542f235a..e92188acd08 100644 --- a/examples/gno.land/p/demo/forms/validate.gno +++ b/examples/gno.land/p/demo/forms/validate.gno @@ -115,7 +115,7 @@ func ValidateAnswers(answers string, fields []Field) bool { if err != nil { return false } - + if answer.IsNull() && !field.Required { return true } diff --git a/examples/gno.land/p/demo/forms/validate_test.gno b/examples/gno.land/p/demo/forms/validate_test.gno index a99172feb12..e10ce919fdf 100644 --- a/examples/gno.land/p/demo/forms/validate_test.gno +++ b/examples/gno.land/p/demo/forms/validate_test.gno @@ -46,73 +46,73 @@ func TestAnswerFormInvalidForm(t *testing.T) { name string answer string expectPanic bool - data string + data string }{ { name: "correct", answer: `["Alex", 21, true, 0, [0, 1]]`, expectPanic: false, - data: dataAllTypes, + data: dataAllTypes, }, { name: "invalid string", answer: `[0, 21, true, 0, [0, 1]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "invalid number", answer: `["Alex", "21", true, 0, [0, 1]]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "invalid boolean", answer: `["Alex", 21, 1, 0, [0, 1]]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "invalid choice", answer: `["Alex", 21, true, 10, [0, 1]]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "invalid multi-choice 1", answer: `["Alex", 21, true, 0, [0, 1, 2, 3, 4, 5]]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "invalid multi-choice 2", answer: `["Alex", 21, true, 0, [5]]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "invalid multi-choice 3", answer: `["Alex", 21, true, 0, 0]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "required string", answer: `["", 21, true, 0, [0, 1]]`, expectPanic: true, - data: dataAllTypes, + data: dataAllTypes, }, { name: "unrequired number", answer: `["Alex", null, true, 0, [0, 1]]`, expectPanic: false, - data: dataAllTypes, + data: dataAllTypes, }, { - name: "correct one field", - answer: `["Alex"]`, + name: "correct one field", + answer: `["Alex"]`, expectPanic: false, - data: dataOneRequiredText, + data: dataOneRequiredText, }, } From 92797f134119f1b4a959d71e6379681b8e1997b8 Mon Sep 17 00:00:00 2001 From: Alex Gherasie <68433935+agherasie@users.noreply.github.com> Date: Sun, 21 Jul 2024 11:41:22 +0300 Subject: [PATCH 06/20] Update examples/gno.land/p/demo/forms/submit_test.gno Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- examples/gno.land/p/demo/forms/submit_test.gno | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index 0d57ccfd71d..8c16bab4635 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -2,7 +2,6 @@ package forms import ( "time" - "testing" ) From 82dd48322f3f6084bef39afd566fb072aa74674e Mon Sep 17 00:00:00 2001 From: Alex Gherasie <68433935+agherasie@users.noreply.github.com> Date: Sun, 21 Jul 2024 11:41:46 +0300 Subject: [PATCH 07/20] Update examples/gno.land/p/demo/forms/create_test.gno Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- examples/gno.land/p/demo/forms/create_test.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/forms/create_test.gno b/examples/gno.land/p/demo/forms/create_test.gno index 2ede4a54592..1f208a10fba 100644 --- a/examples/gno.land/p/demo/forms/create_test.gno +++ b/examples/gno.land/p/demo/forms/create_test.gno @@ -61,7 +61,7 @@ func TestCreateForm(t *testing.T) { t.Error("Description is not correct") } if len(form.Fields) != 5 { - t.Error("Fields are not correct") + t.Error("Not enough fields were provided") } if form.Fields[0].Label != "Name" { t.Error("Field 0 label is not correct") From 9504f6f214d1412b997b2e16bf71a0972e41f632 Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 11:44:33 +0300 Subject: [PATCH 08/20] fix(examples): newlines in gno-forms structs --- examples/gno.land/p/demo/forms/forms.gno | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index 65975ad59b4..a85dfa2eebd 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -10,27 +10,16 @@ import ( type Field struct { Label string - - /* - string: "string"; - number: "number"; - boolean: "boolean"; - choice: "['Pizza', 'Schnitzel', 'Burger']"; - multi-choice: "{'Pizza', 'Schnitzel', 'Burger'}"; - */ FieldType string - Required bool } type Form struct { ID string Owner std.Address - Title string Description string Fields []Field - CreatedAt time.Time openAt *time.Time closeAt *time.Time @@ -38,20 +27,14 @@ type Form struct { type Submission struct { FormID string - Author std.Address - - /* ["Alex", 21, true, 0, [0, 1]] */ Answers string // json - SubmittedAt time.Time } type FormDatabase struct { Forms []*Form - Answers []*Submission - IDCounter seqid.ID } From 254c10fb363ca811410c0caea4e768b4e5ca959f Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 11:48:28 +0300 Subject: [PATCH 09/20] fix(examples): FormDB rename gno-forms --- examples/gno.land/p/demo/forms/create.gno | 2 +- examples/gno.land/p/demo/forms/create_test.gno | 2 +- examples/gno.land/p/demo/forms/forms.gno | 12 ++++++------ examples/gno.land/p/demo/forms/submit.gno | 2 +- examples/gno.land/p/demo/forms/submit_test.gno | 4 ++-- examples/gno.land/p/demo/forms/validate_test.gno | 2 +- examples/gno.land/r/demo/forms/forms.gno | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index c1da4d108fc..f7ea6c1fc75 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -42,7 +42,7 @@ func ParseDates(openAt string, closeAt string) (*time.Time, *time.Time) { return openAtTime, closeAtTime } -func (db *FormDatabase) CreateForm(title string, description string, openAt string, closeAt string, data string) string { +func (db *FormDB) CreateForm(title string, description string, openAt string, closeAt string, data string) string { // Parsing dates openAtTime, closeAtTime := ParseDates(openAt, closeAt) diff --git a/examples/gno.land/p/demo/forms/create_test.gno b/examples/gno.land/p/demo/forms/create_test.gno index 1f208a10fba..7348a5a9518 100644 --- a/examples/gno.land/p/demo/forms/create_test.gno +++ b/examples/gno.land/p/demo/forms/create_test.gno @@ -42,7 +42,7 @@ func TestCreateForm(t *testing.T) { "required": true } ]` - db := NewDatabase() + db := NewDB() id := db.CreateForm(title, description, openAt, closeAt, data) if id == "" { t.Error("Form ID is empty") diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index a85dfa2eebd..7e9f24412e0 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -32,14 +32,14 @@ type Submission struct { SubmittedAt time.Time } -type FormDatabase struct { +type FormDB struct { Forms []*Form Answers []*Submission IDCounter seqid.ID } -func NewDatabase() *FormDatabase { - return &FormDatabase{ +func NewDB() *FormDB { + return &FormDB{ Forms: make([]*Form, 0), Answers: make([]*Submission, 0), } @@ -83,7 +83,7 @@ func (form *Form) CloseAt() (time.Time, error) { return *form.closeAt, nil } -func (db *FormDatabase) GetForm(id string) *Form { +func (db *FormDB) GetForm(id string) *Form { for _, form := range db.Forms { if form.ID == id { return form @@ -92,7 +92,7 @@ func (db *FormDatabase) GetForm(id string) *Form { return nil } -func (db *FormDatabase) GetAnswer(formID string, author std.Address) *Submission { +func (db *FormDB) GetAnswer(formID string, author std.Address) *Submission { for _, answer := range db.Answers { if answer.FormID == formID && answer.Author.String() == author.String() { return answer @@ -101,7 +101,7 @@ func (db *FormDatabase) GetAnswer(formID string, author std.Address) *Submission return nil } -func (db *FormDatabase) GetSubmissionsByFormID(formID string) []*Submission { +func (db *FormDB) GetSubmissionsByFormID(formID string) []*Submission { submissions := make([]*Submission, 0) for _, answer := range db.Answers { diff --git a/examples/gno.land/p/demo/forms/submit.gno b/examples/gno.land/p/demo/forms/submit.gno index 4aa4d6fbd28..a5a654f07d0 100644 --- a/examples/gno.land/p/demo/forms/submit.gno +++ b/examples/gno.land/p/demo/forms/submit.gno @@ -7,7 +7,7 @@ import ( "gno.land/p/demo/ufmt" ) -func (db *FormDatabase) SubmitForm(formID string, answers string) { +func (db *FormDB) SubmitForm(formID string, answers string) { // Check if form exists form := db.GetForm(formID) if form == nil { diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index 8c16bab4635..d9541e887c2 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -6,7 +6,7 @@ import ( ) func TestAnswerForm(t *testing.T) { - db := NewDatabase() + db := NewDB() data := `[ { @@ -64,7 +64,7 @@ func shouldPanic(t *testing.T, f func()) { } func TestAnswerFormDates(t *testing.T) { - db := NewDatabase() + db := NewDB() now := time.Now() tomorrow := now.AddDate(0, 0, 1).Format("2006-01-02T15:04:05Z") diff --git a/examples/gno.land/p/demo/forms/validate_test.gno b/examples/gno.land/p/demo/forms/validate_test.gno index e10ce919fdf..39012839a1e 100644 --- a/examples/gno.land/p/demo/forms/validate_test.gno +++ b/examples/gno.land/p/demo/forms/validate_test.gno @@ -5,7 +5,7 @@ import ( ) func TestAnswerFormInvalidForm(t *testing.T) { - db := NewDatabase() + db := NewDB() dataAllTypes := `[ { diff --git a/examples/gno.land/r/demo/forms/forms.gno b/examples/gno.land/r/demo/forms/forms.gno index ad54cf88d8c..b18ad0a79dd 100644 --- a/examples/gno.land/r/demo/forms/forms.gno +++ b/examples/gno.land/r/demo/forms/forms.gno @@ -9,10 +9,10 @@ import ( "gno.land/p/demo/ufmt" ) -var db *forms.FormDatabase +var db *forms.FormDB func init() { - db = forms.NewDatabase() + db = forms.NewDB() } func CreateForm(title string, description string, openAt string, closeAt string, data string) string { From 76afc4662e7e2e1097c6f0492c1b4834b7dc61ae Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 11:58:37 +0300 Subject: [PATCH 10/20] fix(examples): gno-forms unexported functions --- examples/gno.land/p/demo/forms/create.gno | 4 ++-- examples/gno.land/p/demo/forms/validate.gno | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index f7ea6c1fc75..5826a3c946d 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -16,7 +16,7 @@ func CreateField(label string, fieldType string, required bool) Field { } } -func ParseDates(openAt string, closeAt string) (*time.Time, *time.Time) { +func parseDates(openAt string, closeAt string) (*time.Time, *time.Time) { var openAtTime, closeAtTime *time.Time dateFormat := "2006-01-02T15:04:05Z" @@ -44,7 +44,7 @@ func ParseDates(openAt string, closeAt string) (*time.Time, *time.Time) { func (db *FormDB) CreateForm(title string, description string, openAt string, closeAt string, data string) string { // Parsing dates - openAtTime, closeAtTime := ParseDates(openAt, closeAt) + openAtTime, closeAtTime := parseDates(openAt, closeAt) // Parsing the json submission node, err := json.Unmarshal([]byte(data)) diff --git a/examples/gno.land/p/demo/forms/validate.gno b/examples/gno.land/p/demo/forms/validate.gno index e92188acd08..359e0a13380 100644 --- a/examples/gno.land/p/demo/forms/validate.gno +++ b/examples/gno.land/p/demo/forms/validate.gno @@ -6,7 +6,7 @@ import ( "gno.land/p/demo/json" ) -func ValidateBooleanField(node *json.Node, field Field) bool { +func validateBooleanField(node *json.Node, field Field) bool { if node.IsBool() == false { return false } @@ -24,7 +24,7 @@ func ValidateBooleanField(node *json.Node, field Field) bool { return true } -func ValidateStringField(node *json.Node, field Field) bool { +func validateStringField(node *json.Node, field Field) bool { if node.IsString() == false { return false } @@ -42,7 +42,7 @@ func ValidateStringField(node *json.Node, field Field) bool { return true } -func ValidateNumberField(node *json.Node, field Field) bool { +func validateNumberField(node *json.Node, field Field) bool { if node.IsNumber() == false { return false } @@ -55,7 +55,7 @@ func ValidateNumberField(node *json.Node, field Field) bool { return true } -func ValidateMultiChoiceField(node *json.Node, field Field) bool { +func validateMultiChoiceField(node *json.Node, field Field) bool { choices := strings.Split(field.FieldType[1:len(field.FieldType)-1], "|") if node.IsArray() == false { @@ -85,7 +85,7 @@ func ValidateMultiChoiceField(node *json.Node, field Field) bool { return true } -func ValideChoiceField(node *json.Node, field Field) bool { +func validateChoiceField(node *json.Node, field Field) bool { choices := strings.Split(field.FieldType[1:len(field.FieldType)-1], "|") if node.IsNumber() == false { @@ -121,23 +121,23 @@ func ValidateAnswers(answers string, fields []Field) bool { } if field.FieldType == "boolean" { - if ValidateBooleanField(answer, field) == false { + if validateBooleanField(answer, field) == false { return false } } else if field.FieldType == "string" { - if ValidateStringField(answer, field) == false { + if validateStringField(answer, field) == false { return false } } else if field.FieldType == "number" { - if ValidateNumberField(answer, field) == false { + if validateNumberField(answer, field) == false { return false } } else if field.FieldType[0] == '{' && field.FieldType[len(field.FieldType)-1] == '}' { - if ValidateMultiChoiceField(answer, field) == false { + if validateMultiChoiceField(answer, field) == false { return false } } else if field.FieldType[0] == '[' && field.FieldType[len(field.FieldType)-1] == ']' { - if ValideChoiceField(answer, field) == false { + if validateChoiceField(answer, field) == false { return false } } else { From 6e35760095386660694c5b040346b97e394f1da9 Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 12:28:27 +0300 Subject: [PATCH 11/20] fix(examples): gno-forms error constants --- examples/gno.land/p/demo/forms/create.gno | 31 ++++++++++------- .../gno.land/p/demo/forms/create_test.gno | 11 +++--- examples/gno.land/p/demo/forms/errors.gno | 15 ++++++++ examples/gno.land/p/demo/forms/forms.gno | 17 +++++----- examples/gno.land/p/demo/forms/submit.gno | 16 ++++----- .../gno.land/p/demo/forms/submit_test.gno | 20 ++++++++--- .../gno.land/p/demo/forms/validate_test.gno | 5 ++- examples/gno.land/r/demo/forms/forms.gno | 34 +++++++++++-------- 8 files changed, 95 insertions(+), 54 deletions(-) create mode 100644 examples/gno.land/p/demo/forms/errors.gno diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index 5826a3c946d..9258bb72a6e 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -5,7 +5,6 @@ import ( "time" "gno.land/p/demo/json" - "gno.land/p/demo/ufmt" ) func CreateField(label string, fieldType string, required bool) Field { @@ -16,7 +15,12 @@ func CreateField(label string, fieldType string, required bool) Field { } } -func parseDates(openAt string, closeAt string) (*time.Time, *time.Time) { +type times struct { + openAt *time.Time + closeAt *time.Time +} + +func parseDates(openAt string, closeAt string) (times, error) { var openAtTime, closeAtTime *time.Time dateFormat := "2006-01-02T15:04:05Z" @@ -25,7 +29,7 @@ func parseDates(openAt string, closeAt string) (*time.Time, *time.Time) { if openAt != "" { res, err := time.Parse(dateFormat, openAt) if err != nil { - panic(ufmt.Errorf("invalid date: %v", openAt)) + return times{}, errInvalidDate } openAtTime = &res } @@ -34,22 +38,25 @@ func parseDates(openAt string, closeAt string) (*time.Time, *time.Time) { if closeAt != "" { res, err := time.Parse(dateFormat, closeAt) if err != nil { - panic(ufmt.Errorf("invalid date: %v", closeAt)) + return times{}, errInvalidDate } closeAtTime = &res } - return openAtTime, closeAtTime + return times{openAtTime, closeAtTime}, nil } -func (db *FormDB) CreateForm(title string, description string, openAt string, closeAt string, data string) string { +func (db *FormDB) CreateForm(title string, description string, openAt string, closeAt string, data string) (string, error) { // Parsing dates - openAtTime, closeAtTime := parseDates(openAt, closeAt) + times, err := parseDates(openAt, closeAt) + if err != nil { + return "", err + } // Parsing the json submission node, err := json.Unmarshal([]byte(data)) if err != nil { - ufmt.Errorf("invalid json: %v", err) + return "", errInvalidJson } fieldsCount := node.Size() @@ -59,7 +66,7 @@ func (db *FormDB) CreateForm(title string, description string, openAt string, cl for i := 0; i < fieldsCount; i++ { field, err := node.GetIndex(i) if err != nil { - ufmt.Errorf("error: %v", err) + return "", errInvalidJson } labelNode, _ := field.GetKey("label") @@ -83,13 +90,13 @@ func (db *FormDB) CreateForm(title string, description string, openAt string, cl Title: title, Description: description, CreatedAt: time.Now(), - openAt: openAtTime, - closeAt: closeAtTime, + openAt: times.openAt, + closeAt: times.closeAt, Fields: fields, } // Adding the form to the database db.Forms = append(db.Forms, &form) - return id + return id, nil } diff --git a/examples/gno.land/p/demo/forms/create_test.gno b/examples/gno.land/p/demo/forms/create_test.gno index 7348a5a9518..660d7bb7c0c 100644 --- a/examples/gno.land/p/demo/forms/create_test.gno +++ b/examples/gno.land/p/demo/forms/create_test.gno @@ -43,13 +43,16 @@ func TestCreateForm(t *testing.T) { } ]` db := NewDB() - id := db.CreateForm(title, description, openAt, closeAt, data) + id, err := db.CreateForm(title, description, openAt, closeAt, data) + if err != nil { + t.Error(err) + } if id == "" { t.Error("Form ID is empty") } - form := db.GetForm(id) - if form == nil { - t.Error("Form is nil") + form, err := db.GetForm(id) + if err != nil { + t.Error(err) } if form.Owner != alice { t.Error("Owner is not correct") diff --git a/examples/gno.land/p/demo/forms/errors.gno b/examples/gno.land/p/demo/forms/errors.gno new file mode 100644 index 00000000000..e487dbbf16f --- /dev/null +++ b/examples/gno.land/p/demo/forms/errors.gno @@ -0,0 +1,15 @@ +package forms + +import "errors" + +var ( + errNoOpenDate = errors.New("Form has no open date") + errNoCloseDate = errors.New("Form has no close date") + errInvalidJson = errors.New("Invalid JSON") + errInvalidDate = errors.New("Invalid date") + errFormNotFound = errors.New("Form not found") + errAnswerNotFound = errors.New("Answer not found") + errAlreadySubmitted = errors.New("You already submitted this form") + errFormClosed = errors.New("Form is closed") + errInvalidAnswers = errors.New("Invalid answers") +) \ No newline at end of file diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index 7e9f24412e0..1c12078cff1 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -1,7 +1,6 @@ package forms import ( - "errors" "std" "time" @@ -69,7 +68,7 @@ func (form *Form) IsOpen() bool { func (form *Form) OpenAt() (time.Time, error) { if form.openAt == nil { - return time.Time{}, errors.New("Form has no open date") + return time.Time{}, errNoOpenDate } return *form.openAt, nil @@ -77,28 +76,28 @@ func (form *Form) OpenAt() (time.Time, error) { func (form *Form) CloseAt() (time.Time, error) { if form.closeAt == nil { - return time.Time{}, errors.New("Form has no close date") + return time.Time{}, errNoCloseDate } return *form.closeAt, nil } -func (db *FormDB) GetForm(id string) *Form { +func (db *FormDB) GetForm(id string) (*Form, error) { for _, form := range db.Forms { if form.ID == id { - return form + return form, nil } } - return nil + return nil, errFormNotFound } -func (db *FormDB) GetAnswer(formID string, author std.Address) *Submission { +func (db *FormDB) GetAnswer(formID string, author std.Address) (*Submission, error) { for _, answer := range db.Answers { if answer.FormID == formID && answer.Author.String() == author.String() { - return answer + return answer, nil } } - return nil + return nil, errAnswerNotFound } func (db *FormDB) GetSubmissionsByFormID(formID string) []*Submission { diff --git a/examples/gno.land/p/demo/forms/submit.gno b/examples/gno.land/p/demo/forms/submit.gno index a5a654f07d0..bb8b55e9980 100644 --- a/examples/gno.land/p/demo/forms/submit.gno +++ b/examples/gno.land/p/demo/forms/submit.gno @@ -3,31 +3,29 @@ package forms import ( "std" "time" - - "gno.land/p/demo/ufmt" ) func (db *FormDB) SubmitForm(formID string, answers string) { // Check if form exists - form := db.GetForm(formID) - if form == nil { - panic(ufmt.Errorf("Form not found: %s", formID)) + form, err := db.GetForm(formID) + if err != nil { + panic(err) } // Check if form was already submitted by this user - previousAnswer := db.GetAnswer(formID, std.PrevRealm().Addr()) + previousAnswer, err := db.GetAnswer(formID, std.PrevRealm().Addr()) if previousAnswer != nil { - panic("You already submitted this form") + panic(errAlreadySubmitted) } // Check time restrictions if !form.IsOpen() { - panic("Form is closed") + panic(errFormClosed) } // Check if answers are formatted correctly if ValidateAnswers(answers, form.Fields) == false { - panic("Invalid answers") + panic(errInvalidAnswers) } // Save answers diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index d9541e887c2..0e0142a760b 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -36,7 +36,10 @@ func TestAnswerForm(t *testing.T) { } ]` - formID := db.CreateForm("Test Form", "Test Description", "", "", data) + formID, err := db.CreateForm("Test Form", "Test Description", "", "", data) + if err != nil { + t.Error(err) + } answers := `["Alex", 21, true, 0, [0, 1]]` db.SubmitForm(formID, answers) @@ -80,15 +83,24 @@ func TestAnswerFormDates(t *testing.T) { answers := `["Test"]` shouldPanic(t, func() { - formID := db.CreateForm("Test Form", "Test Description", tomorrow, "", data) + formID, err := db.CreateForm("Test Form", "Test Description", tomorrow, "", data) + if err != nil { + t.Error(err) + } db.SubmitForm(formID, answers) }) shouldPanic(t, func() { - formID := db.CreateForm("Test Form", "Test Description", "", yesterday, data) + formID, err := db.CreateForm("Test Form", "Test Description", "", yesterday, data) + if err != nil { + t.Error(err) + } db.SubmitForm(formID, answers) }) - formID := db.CreateForm("Test Form", "Test Description", yesterday, tomorrow, data) + formID, err := db.CreateForm("Test Form", "Test Description", yesterday, tomorrow, data) + if err != nil { + t.Error(err) + } db.SubmitForm(formID, answers) } diff --git a/examples/gno.land/p/demo/forms/validate_test.gno b/examples/gno.land/p/demo/forms/validate_test.gno index 39012839a1e..952cb6d96d4 100644 --- a/examples/gno.land/p/demo/forms/validate_test.gno +++ b/examples/gno.land/p/demo/forms/validate_test.gno @@ -118,7 +118,10 @@ func TestAnswerFormInvalidForm(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - formID := db.CreateForm("Test Form", "Test Description", "", "", tt.data) + formID, err := db.CreateForm("Test Form", "Test Description", "", "", tt.data) + if err != nil { + t.Error(err) + } defer func() { if r := recover(); r != nil { diff --git a/examples/gno.land/r/demo/forms/forms.gno b/examples/gno.land/r/demo/forms/forms.gno index b18ad0a79dd..beaa770e401 100644 --- a/examples/gno.land/r/demo/forms/forms.gno +++ b/examples/gno.land/r/demo/forms/forms.gno @@ -16,7 +16,11 @@ func init() { } func CreateForm(title string, description string, openAt string, closeAt string, data string) string { - return db.CreateForm(title, description, openAt, closeAt, data) + id, err := db.CreateForm(title, description, openAt, closeAt, data) + if err != nil { + panic(err) + } + return id } func GetForms() string { @@ -45,16 +49,16 @@ func GetForms() string { encoded, err := json.Marshal(formsJson) if err != nil { - panic(ufmt.Errorf("error: %v", err)) + panic(err) } return string(encoded) } func GetFormByID(id string) string { - form := db.GetForm(id) - if form == nil { - panic(ufmt.Errorf("Form not found: %s", id)) + form, err := db.GetForm(id) + if err != nil { + panic(err) } fieldsJson := json.ArrayNode("", []*json.Node{}) @@ -98,30 +102,30 @@ func GetFormByID(id string) string { encoded, err := json.Marshal(jsonRes) if err != nil { - panic(ufmt.Errorf("error: %v", err)) + panic(err) } return string(encoded) } func GetAnswer(formID string, authorID string) string { - form := db.GetForm(formID) - if form == nil { - panic(ufmt.Errorf("Form not found: %s", formID)) + _, err := db.GetForm(formID) + if err != nil { + panic(err) } - answer := db.GetAnswer(formID, std.Address(authorID)) - if answer == nil { - panic(ufmt.Errorf("Answer not found: %s", authorID)) + answer, err := db.GetAnswer(formID, std.Address(authorID)) + if answer != nil { + panic(err) } return answer.Answers } func SubmitForm(formID string, answers string) { - form := db.GetForm(formID) - if form == nil { - panic(ufmt.Errorf("Form not found: %s", formID)) + _, err := db.GetForm(formID) + if err != nil { + panic(err) } db.SubmitForm(formID, answers) From f0a0e585f1bbba47cbff41c010c9e5deaa0e4f5c Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 12:33:09 +0300 Subject: [PATCH 12/20] fix(examples): gno-forms handling all invalid json errors --- examples/gno.land/p/demo/forms/create.gno | 30 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index 9258bb72a6e..0209fb6464a 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -69,13 +69,31 @@ func (db *FormDB) CreateForm(title string, description string, openAt string, cl return "", errInvalidJson } - labelNode, _ := field.GetKey("label") - fieldTypeNode, _ := field.GetKey("fieldType") - requiredNode, _ := field.GetKey("required") + labelNode, err := field.GetKey("label") + if err != nil { + return "", errInvalidJson + } + fieldTypeNode, err := field.GetKey("fieldType") + if err != nil { + return "", errInvalidJson + } + requiredNode, err := field.GetKey("required") + if err != nil { + return "", errInvalidJson + } - label, _ := labelNode.GetString() - fieldType, _ := fieldTypeNode.GetString() - required, _ := requiredNode.GetBool() + label, err := labelNode.GetString() + if err != nil { + return "", errInvalidJson + } + fieldType, err := fieldTypeNode.GetString() + if err != nil { + return "", errInvalidJson + } + required, err := requiredNode.GetBool() + if err != nil { + return "", errInvalidJson + } fields[i] = CreateField(label, fieldType, required) } From 8d97b6d871a495754bc8aaa279132c82b6e183ce Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 13:04:11 +0300 Subject: [PATCH 13/20] feat(examples): gno-forms using urequire --- .../gno.land/p/demo/forms/create_test.gno | 66 ++++++++----------- .../gno.land/p/demo/forms/submit_test.gno | 42 ++++-------- 2 files changed, 40 insertions(+), 68 deletions(-) diff --git a/examples/gno.land/p/demo/forms/create_test.gno b/examples/gno.land/p/demo/forms/create_test.gno index 660d7bb7c0c..516d8a5ea7f 100644 --- a/examples/gno.land/p/demo/forms/create_test.gno +++ b/examples/gno.land/p/demo/forms/create_test.gno @@ -5,12 +5,13 @@ import ( "testing" "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" ) func TestCreateForm(t *testing.T) { alice := testutils.TestAddress("alice") std.TestSetOrigCaller(alice) - + db := NewDB() title := "Simple Form" description := "This is a form" openAt := "2021-01-01T00:00:00Z" @@ -42,43 +43,28 @@ func TestCreateForm(t *testing.T) { "required": true } ]` - db := NewDB() - id, err := db.CreateForm(title, description, openAt, closeAt, data) - if err != nil { - t.Error(err) - } - if id == "" { - t.Error("Form ID is empty") - } - form, err := db.GetForm(id) - if err != nil { - t.Error(err) - } - if form.Owner != alice { - t.Error("Owner is not correct") - } - if form.Title != title { - t.Error("Title is not correct") - } - if form.Description != description { - t.Error("Description is not correct") - } - if len(form.Fields) != 5 { - t.Error("Not enough fields were provided") - } - if form.Fields[0].Label != "Name" { - t.Error("Field 0 label is not correct") - } - if form.Fields[0].FieldType != "string" { - t.Error("Field 0 type is not correct") - } - if form.Fields[0].Required != true { - t.Error("Field 0 required is not correct") - } - if form.Fields[1].Label != "Age" { - t.Error("Field 1 label is not correct") - } - if form.Fields[1].FieldType != "number" { - t.Error("Field 1 type is not correct") - } + + urequire.NotPanics(t, func() { + id, err := db.CreateForm(title, description, openAt, closeAt, data) + if err != nil { + panic(err) + } + urequire.True(t, id != "", "Form ID is empty") + + form, err := db.GetForm(id) + if err != nil { + panic(err) + } + + urequire.True(t, form.ID == id, "Form ID is not correct") + urequire.True(t, form.Owner == alice, "Owner is not correct") + urequire.True(t, form.Title == title, "Title is not correct") + urequire.True(t, form.Description == description, "Description is not correct") + urequire.True(t, len(form.Fields) == 5, "Not enough fields were provided") + urequire.True(t, form.Fields[0].Label == "Name", "Field 0 label is not correct") + urequire.True(t, form.Fields[0].FieldType == "string", "Field 0 type is not correct") + urequire.True(t, form.Fields[0].Required == true, "Field 0 required is not correct") + urequire.True(t, form.Fields[1].Label == "Age", "Field 1 label is not correct") + urequire.True(t, form.Fields[1].FieldType == "number", "Field 1 type is not correct") + }) } diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index 0e0142a760b..ef4948fa3c7 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -3,6 +3,8 @@ package forms import ( "time" "testing" + + "gno.land/p/demo/urequire" ) func TestAnswerForm(t *testing.T) { @@ -41,29 +43,11 @@ func TestAnswerForm(t *testing.T) { t.Error(err) } answers := `["Alex", 21, true, 0, [0, 1]]` - db.SubmitForm(formID, answers) - if len(db.Answers) != 1 { - t.Errorf("Expected 1 answer, got %d", len(db.Answers)) - } - - if db.Answers[0].FormID != formID { - t.Errorf("Expected form ID %s, got %s", formID, db.Answers[0].FormID) - } - - if db.Answers[0].Answers != answers { - t.Errorf("Expected answers %s, got %s", answers, db.Answers[0].Answers) - } -} - -func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("should have panic") - } - }() - f() + urequire.True(t, len(db.Answers) == 1, "Expected 1 answer, got", string(len(db.Answers))) + urequire.True(t, db.Answers[0].FormID == formID, "Expected form ID", formID, "got", db.Answers[0].FormID) + urequire.True(t, db.Answers[0].Answers == answers, "Expected answers", answers, "got", db.Answers[0].Answers) } func TestAnswerFormDates(t *testing.T) { @@ -82,7 +66,7 @@ func TestAnswerFormDates(t *testing.T) { ]` answers := `["Test"]` - shouldPanic(t, func() { + urequire.PanicsWithMessage(t, "Form is closed", func() { formID, err := db.CreateForm("Test Form", "Test Description", tomorrow, "", data) if err != nil { t.Error(err) @@ -90,7 +74,7 @@ func TestAnswerFormDates(t *testing.T) { db.SubmitForm(formID, answers) }) - shouldPanic(t, func() { + urequire.PanicsWithMessage(t, "Form is closed", func() { formID, err := db.CreateForm("Test Form", "Test Description", "", yesterday, data) if err != nil { t.Error(err) @@ -98,9 +82,11 @@ func TestAnswerFormDates(t *testing.T) { db.SubmitForm(formID, answers) }) - formID, err := db.CreateForm("Test Form", "Test Description", yesterday, tomorrow, data) - if err != nil { - t.Error(err) - } - db.SubmitForm(formID, answers) + urequire.NotPanics(t, func() { + formID, err := db.CreateForm("Test Form", "Test Description", yesterday, tomorrow, data) + if (err != nil) { + panic(err) + } + db.SubmitForm(formID, answers) + }) } From c6f0b15289693e96430c5a051da40dfdfa189d33 Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 13:06:51 +0300 Subject: [PATCH 14/20] chore(examples): gno-forms fmt --- examples/gno.land/p/demo/forms/errors.gno | 18 +++++++------- examples/gno.land/p/demo/forms/forms.gno | 24 +++++++++---------- .../gno.land/p/demo/forms/submit_test.gno | 4 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/examples/gno.land/p/demo/forms/errors.gno b/examples/gno.land/p/demo/forms/errors.gno index e487dbbf16f..e1d0dabd932 100644 --- a/examples/gno.land/p/demo/forms/errors.gno +++ b/examples/gno.land/p/demo/forms/errors.gno @@ -3,13 +3,13 @@ package forms import "errors" var ( - errNoOpenDate = errors.New("Form has no open date") - errNoCloseDate = errors.New("Form has no close date") - errInvalidJson = errors.New("Invalid JSON") - errInvalidDate = errors.New("Invalid date") - errFormNotFound = errors.New("Form not found") - errAnswerNotFound = errors.New("Answer not found") + errNoOpenDate = errors.New("Form has no open date") + errNoCloseDate = errors.New("Form has no close date") + errInvalidJson = errors.New("Invalid JSON") + errInvalidDate = errors.New("Invalid date") + errFormNotFound = errors.New("Form not found") + errAnswerNotFound = errors.New("Answer not found") errAlreadySubmitted = errors.New("You already submitted this form") - errFormClosed = errors.New("Form is closed") - errInvalidAnswers = errors.New("Invalid answers") -) \ No newline at end of file + errFormClosed = errors.New("Form is closed") + errInvalidAnswers = errors.New("Invalid answers") +) diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index 1c12078cff1..a0e4b34d5a1 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -8,32 +8,32 @@ import ( ) type Field struct { - Label string + Label string FieldType string - Required bool + Required bool } type Form struct { - ID string - Owner std.Address + ID string + Owner std.Address Title string Description string Fields []Field - CreatedAt time.Time - openAt *time.Time - closeAt *time.Time + CreatedAt time.Time + openAt *time.Time + closeAt *time.Time } type Submission struct { - FormID string - Author std.Address - Answers string // json + FormID string + Author std.Address + Answers string // json SubmittedAt time.Time } type FormDB struct { - Forms []*Form - Answers []*Submission + Forms []*Form + Answers []*Submission IDCounter seqid.ID } diff --git a/examples/gno.land/p/demo/forms/submit_test.gno b/examples/gno.land/p/demo/forms/submit_test.gno index ef4948fa3c7..e5125acbae2 100644 --- a/examples/gno.land/p/demo/forms/submit_test.gno +++ b/examples/gno.land/p/demo/forms/submit_test.gno @@ -1,8 +1,8 @@ package forms import ( - "time" "testing" + "time" "gno.land/p/demo/urequire" ) @@ -84,7 +84,7 @@ func TestAnswerFormDates(t *testing.T) { urequire.NotPanics(t, func() { formID, err := db.CreateForm("Test Form", "Test Description", yesterday, tomorrow, data) - if (err != nil) { + if err != nil { panic(err) } db.SubmitForm(formID, answers) From 361170ae0e657adfa4c841faf839c7bbf0343cc9 Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 13:24:42 +0300 Subject: [PATCH 15/20] chore(examples): gno-forms godoc --- examples/gno.land/p/demo/forms/create.gno | 1 + examples/gno.land/p/demo/forms/forms.gno | 18 ++++++++++++++++++ examples/gno.land/p/demo/forms/submit.gno | 1 + examples/gno.land/p/demo/forms/validate.gno | 1 + 4 files changed, 21 insertions(+) diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index 0209fb6464a..a80a3380830 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -46,6 +46,7 @@ func parseDates(openAt string, closeAt string) (times, error) { return times{openAtTime, closeAtTime}, nil } +// CreateForm creates a new form with the given parameters func (db *FormDB) CreateForm(title string, description string, openAt string, closeAt string, data string) (string, error) { // Parsing dates times, err := parseDates(openAt, closeAt) diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index a0e4b34d5a1..acf38a424ca 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -7,6 +7,12 @@ import ( "gno.land/p/demo/seqid" ) +// FieldType examples : +// - string: "string"; +// - number: "number"; +// - boolean: "boolean"; +// - choice: "['Pizza', 'Schnitzel', 'Burger']"; +// - multi-choice: "{'Pizza', 'Schnitzel', 'Burger'}"; type Field struct { Label string FieldType string @@ -24,6 +30,8 @@ type Form struct { closeAt *time.Time } +// Answers example : +// - ["Alex", 21, true, 0, [0, 1]] type Submission struct { FormID string Author std.Address @@ -44,6 +52,11 @@ func NewDB() *FormDB { } } +// This function checks if the form is open by verifying the given dates +// - If a form doesn't have any dates, it's considered open +// - If a form has only an open date, it's considered open if the open date is in the past +// - If a form has only a close date, it's considered open if the close date is in the future +// - If a form has both open and close dates, it's considered open if the current date is between the open and close dates func (form *Form) IsOpen() bool { openAt, errOpen := form.OpenAt() closedAt, errClose := form.CloseAt() @@ -66,6 +79,7 @@ func (form *Form) IsOpen() bool { return time.Now().After(openAt) && time.Now().Before(closedAt) } +// OpenAt returns the open date of the form if it exists func (form *Form) OpenAt() (time.Time, error) { if form.openAt == nil { return time.Time{}, errNoOpenDate @@ -74,6 +88,7 @@ func (form *Form) OpenAt() (time.Time, error) { return *form.openAt, nil } +// CloseAt returns the close date of the form if it exists func (form *Form) CloseAt() (time.Time, error) { if form.closeAt == nil { return time.Time{}, errNoCloseDate @@ -82,6 +97,7 @@ func (form *Form) CloseAt() (time.Time, error) { return *form.closeAt, nil } +// GetForm returns a form by its ID if it exists func (db *FormDB) GetForm(id string) (*Form, error) { for _, form := range db.Forms { if form.ID == id { @@ -91,6 +107,7 @@ func (db *FormDB) GetForm(id string) (*Form, error) { return nil, errFormNotFound } +// GetAnswer returns an answer by its form - and author ids if it exists func (db *FormDB) GetAnswer(formID string, author std.Address) (*Submission, error) { for _, answer := range db.Answers { if answer.FormID == formID && answer.Author.String() == author.String() { @@ -100,6 +117,7 @@ func (db *FormDB) GetAnswer(formID string, author std.Address) (*Submission, err return nil, errAnswerNotFound } +// GetSubmissionsByFormID returns a list containing the existing form submissions by the form ID func (db *FormDB) GetSubmissionsByFormID(formID string) []*Submission { submissions := make([]*Submission, 0) diff --git a/examples/gno.land/p/demo/forms/submit.gno b/examples/gno.land/p/demo/forms/submit.gno index bb8b55e9980..7f78052829b 100644 --- a/examples/gno.land/p/demo/forms/submit.gno +++ b/examples/gno.land/p/demo/forms/submit.gno @@ -5,6 +5,7 @@ import ( "time" ) +// This function allows to submit a form func (db *FormDB) SubmitForm(formID string, answers string) { // Check if form exists form, err := db.GetForm(formID) diff --git a/examples/gno.land/p/demo/forms/validate.gno b/examples/gno.land/p/demo/forms/validate.gno index 359e0a13380..f85972324c3 100644 --- a/examples/gno.land/p/demo/forms/validate.gno +++ b/examples/gno.land/p/demo/forms/validate.gno @@ -100,6 +100,7 @@ func validateChoiceField(node *json.Node, field Field) bool { return true } +// ValidateAnswers checks if the given answers are valid for the given fields func ValidateAnswers(answers string, fields []Field) bool { unmarshalled, err := json.Unmarshal([]byte(answers)) if err != nil { From 2588cb36c39172c6f91fbfe8655a63f982f24dea Mon Sep 17 00:00:00 2001 From: agherasie Date: Sun, 21 Jul 2024 13:32:23 +0300 Subject: [PATCH 16/20] chore(examples): gno-forms tidy --- examples/gno.land/p/demo/forms/gno.mod | 7 ++++--- examples/gno.land/r/demo/forms/gno.mod | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/gno.land/p/demo/forms/gno.mod b/examples/gno.land/p/demo/forms/gno.mod index 0a9eb6dbfc8..d35acf01e69 100644 --- a/examples/gno.land/p/demo/forms/gno.mod +++ b/examples/gno.land/p/demo/forms/gno.mod @@ -1,7 +1,8 @@ module gno.land/p/demo/forms require ( - gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/seqid v0.0.0-latest - gno.land/p/demo/json v0.0.0-latest -) \ No newline at end of file + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/forms/gno.mod b/examples/gno.land/r/demo/forms/gno.mod index 09755e9cd03..9f41bb6410a 100644 --- a/examples/gno.land/r/demo/forms/gno.mod +++ b/examples/gno.land/r/demo/forms/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/demo/forms require ( - gno.land/p/demo/forms v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/json v0.0.0-latest -) \ No newline at end of file + gno.land/p/demo/forms v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) From 9ccffcf6566c494b68e42a0a1d9213e400c5aaf2 Mon Sep 17 00:00:00 2001 From: Alex Gherasie <68433935+agherasie@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:59:53 +0300 Subject: [PATCH 17/20] Update README.md --- examples/gno.land/p/demo/forms/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/p/demo/forms/README.md b/examples/gno.land/p/demo/forms/README.md index d41c6b55af8..ea8cef86ff8 100644 --- a/examples/gno.land/p/demo/forms/README.md +++ b/examples/gno.land/p/demo/forms/README.md @@ -16,9 +16,9 @@ type|example string|`{"label": "Name", "fieldType": "string", "required": true}` number|`{"label": "Age", "fieldType": "number", "required": true}` boolean|`{"label": "Is Student?", "fieldType": "boolean", "required": false}` -choice|`{"label": "Favorite Food", "fieldType": "['Pizza', 'Schnitzel', 'Burger']", "required": true}` -multi-choice|`{"label": "Hobbies", "fieldType": "{'Reading', 'Swimming', 'Gaming'}", "required": false}` +choice|`{"label": "Favorite Food", "fieldType": "[Pizza\|Schnitzel\|Burger]", "required": true}` +multi-choice|`{"label": "Hobbies", "fieldType": "{Reading\|Swimming\|Gaming}", "required": false}` ## Web-app -The external repo where the initial development took place and where you can find the frontend is [here](https://github.com/agherasie/gno-forms). \ No newline at end of file +The external repo where the initial development took place and where you can find the frontend is [here](https://github.com/agherasie/gno-forms). From f4e73782d7c016c480ab07673621a514914b0c9a Mon Sep 17 00:00:00 2001 From: Alex Gherasie <68433935+agherasie@users.noreply.github.com> Date: Fri, 2 Aug 2024 18:10:13 +0300 Subject: [PATCH 18/20] Update examples/gno.land/p/demo/forms/forms.gno Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> --- examples/gno.land/p/demo/forms/forms.gno | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/forms/forms.gno b/examples/gno.land/p/demo/forms/forms.gno index acf38a424ca..29ea2ba9b63 100644 --- a/examples/gno.land/p/demo/forms/forms.gno +++ b/examples/gno.land/p/demo/forms/forms.gno @@ -76,7 +76,8 @@ func (form *Form) IsOpen() bool { return time.Now().After(openAt) } - return time.Now().After(openAt) && time.Now().Before(closedAt) + now := time.Now() + return now.After(openAt) && now.Before(closedAt) } // OpenAt returns the open date of the form if it exists From 8da6592cd61a5999372b2bdd2c348a9e76c48332 Mon Sep 17 00:00:00 2001 From: Alex Gherasie <68433935+agherasie@users.noreply.github.com> Date: Fri, 2 Aug 2024 18:10:35 +0300 Subject: [PATCH 19/20] Update examples/gno.land/p/demo/forms/create.gno Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> --- examples/gno.land/p/demo/forms/create.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/forms/create.gno b/examples/gno.land/p/demo/forms/create.gno index a80a3380830..28b6add42dd 100644 --- a/examples/gno.land/p/demo/forms/create.gno +++ b/examples/gno.land/p/demo/forms/create.gno @@ -23,7 +23,7 @@ type times struct { func parseDates(openAt string, closeAt string) (times, error) { var openAtTime, closeAtTime *time.Time - dateFormat := "2006-01-02T15:04:05Z" + const dateFormat = "2006-01-02T15:04:05Z" // Parse openAt if it's not empty if openAt != "" { From 10e684390177910dad1b5e74ac864b5f5edca590 Mon Sep 17 00:00:00 2001 From: agherasie Date: Fri, 2 Aug 2024 18:16:40 +0300 Subject: [PATCH 20/20] chore(examples): gno forms readme markdown table formatting --- examples/gno.land/p/demo/forms/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/gno.land/p/demo/forms/README.md b/examples/gno.land/p/demo/forms/README.md index ea8cef86ff8..a26d318444d 100644 --- a/examples/gno.land/p/demo/forms/README.md +++ b/examples/gno.land/p/demo/forms/README.md @@ -11,13 +11,13 @@ gno-forms is a package which demonstrates a form editing and sharing application ## Field Types The system supports the following field types: -type|example --|- -string|`{"label": "Name", "fieldType": "string", "required": true}` -number|`{"label": "Age", "fieldType": "number", "required": true}` -boolean|`{"label": "Is Student?", "fieldType": "boolean", "required": false}` -choice|`{"label": "Favorite Food", "fieldType": "[Pizza\|Schnitzel\|Burger]", "required": true}` -multi-choice|`{"label": "Hobbies", "fieldType": "{Reading\|Swimming\|Gaming}", "required": false}` +| type | example | +|--------------|-------------------------------------------------------------------------------------------------| +| string | `{"label": "Name", "fieldType": "string", "required": true}` | +| number | `{"label": "Age", "fieldType": "number", "required": true}` | +| boolean | `{"label": "Is Student?", "fieldType": "boolean", "required": false}` | +| choice | `{"label": "Favorite Food", "fieldType": "['Pizza', 'Schnitzel', 'Burger']", "required": true}` | +| multi-choice | `{"label": "Hobbies", "fieldType": "{'Reading', 'Swimming', 'Gaming'}", "required": false}` | ## Web-app