Skip to content

Commit

Permalink
feat(views): add param to store results (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
rozanecm authored Mar 20, 2024
1 parent 50c0f45 commit 1ab0402
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 7 deletions.
61 changes: 54 additions & 7 deletions database.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/url"
"reflect"
)

type Database struct {
Expand Down Expand Up @@ -269,23 +270,69 @@ type ViewResponse struct {

// View performs a query on a database view with the specified design, view, and parameters.
// It returns a ViewResponse representing the response from the view query.
func (db *Database) View(ctx context.Context, design, view string, params ViewParams) (ViewResponse, error) {
//
// Parameters:
// - ctx: The context for the HTTP request.
// - design: The design document name.
// - view: The name of the view within the design document.
// - params: The parameters for the view query.
// - viewResults: A pointer to a struct where the view results will be unmarshalled.
// The struct must have a "rows" field holding a slice of structs with "id" and "key" JSON fields.
// If params.IncludeDocs is true, the struct must also have a "doc" JSON field.
//
// Returns:
// - error: An error if the view query fails or if the viewResults struct does not meet the requirements.
func (db *Database) View(ctx context.Context, design, view string, params ViewParams, resultVar interface{}) error {
err := CheckStructForJSONFields(resultVar)
if err != nil {
return fmt.Errorf("error checking struct for JSON fields: %w", err)
}

code, responseBytes, err := db.httpClient.Get(ctx, fmt.Sprintf("%s/_design/%s/_view/%s?%s", db.dbName, design, view, params.encode()))
if err != nil {
return ViewResponse{}, fmt.Errorf("error creating design doc: %w", err)
return fmt.Errorf("error creating design doc: %w", err)
}

if code != 200 {
return ViewResponse{}, fmt.Errorf("error getting view: %d - %s", code, string(responseBytes))
return fmt.Errorf("error getting view: %d - %s", code, string(responseBytes))
}

var response ViewResponse
err = json.Unmarshal(responseBytes, &response)
// Unmarshal directly into the provided variable
err = json.Unmarshal(responseBytes, resultVar)
if err != nil {
return ViewResponse{}, fmt.Errorf("error unmarshalling view response: %w", err)
return fmt.Errorf("error unmarshalling into resultVar: %w", err)
}

return nil
}

// CheckStructForJSONFields checks if the provided struct has the required JSON fields.
// It returns an error if the struct does not meet the criteria.
func CheckStructForJSONFields(resultVar interface{}) error {
// Get the type of the struct pointed to by resultVar
structType := reflect.TypeOf(resultVar).Elem()

// Check if the 'Rows' field exists and is of type slice with the expected JSON tag
rowsField, found := structType.FieldByName("Rows")
if !found || rowsField.Type.Kind() != reflect.Slice || rowsField.Tag.Get("json") != "rows" {
return fmt.Errorf("resultVar must be a pointer to a struct with a 'Rows' field of type slice and JSON tag 'rows'")
}

// Check if 'id' and 'key' fields exist and have the expected JSON tags
idField, idFound := structType.FieldByName("ID")
keyField, keyFound := structType.FieldByName("Key")
if !idFound || !keyFound || idField.Tag.Get("json") != "id" || keyField.Tag.Get("json") != "key" {
return fmt.Errorf("resultVar must have 'ID' and 'Key' fields with JSON tags 'id' and 'key'")
}

return response, nil
// Check if 'doc' field is required and present with the expected JSON tag
if docField, docFound := structType.FieldByName("Doc"); docFound {
if docField.Tag.Get("json") != "doc" {
return fmt.Errorf("resultVar must have a 'Doc' field with JSON tag 'doc' when IncludeDocs is true")
}
}

return nil
}

// ViewParams defines a struct to represent the parameters for querying a database view.
Expand Down
145 changes: 145 additions & 0 deletions views_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package couchdb

import "testing"

// Define a test struct for holding test cases
type testCase struct {
Name string
Input interface{}
ShouldErr bool
}

func TestCheckStructForJSONFields(t *testing.T) {
testCases := []testCase{
{
Name: "Valid struct with required fields and JSON tags",
Input: &validStruct{},
ShouldErr: false,
},
{
Name: "Struct missing 'Rows' field",
Input: &missingRowsStruct{},
ShouldErr: true,
},
{
Name: "Struct with 'Rows' field of wrong type",
Input: &wrongTypeRowsStruct{},
ShouldErr: true,
},
{
Name: "Struct with 'Rows' field missing JSON tag",
Input: &missingRowsTagStruct{},
ShouldErr: true,
},
{
Name: "Struct missing 'ID' field",
Input: &missingIDStruct{},
ShouldErr: true,
},
{
Name: "Struct missing 'Key' field",
Input: &missingKeyStruct{},
ShouldErr: true,
},
{
Name: "Struct with 'ID' field missing JSON tag",
Input: &missingIDTagStruct{},
ShouldErr: true,
},
{
Name: "Struct with 'Key' field missing JSON tag",
Input: &missingKeyTagStruct{},
ShouldErr: true,
},
{
Name: "Valid struct with 'Doc' field and JSON tag",
Input: &validDocStruct{},
ShouldErr: false,
},
{
Name: "Struct with 'Doc' field missing JSON tag",
Input: &missingDocTagStruct{},
ShouldErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
err := CheckStructForJSONFields(tc.Input)
if (err != nil) != tc.ShouldErr {
t.Errorf("Expected error: %v, Got error: %v", tc.ShouldErr, err)
}
})
}
}

// Define sample structs for testing

type validStruct struct {
Rows []int `json:"rows"`
ID string `json:"id"`
Key string `json:"key"`
Doc struct{} `json:"doc"`
}

type missingRowsStruct struct {
ID string `json:"id"`
Key string `json:"key"`
Doc struct{} `json:"doc"`
}

type wrongTypeRowsStruct struct {
Rows string `json:"rows"`
ID string `json:"id"`
Key string `json:"key"`
Doc struct{} `json:"doc"`
}

type missingRowsTagStruct struct {
Rows []int
ID string `json:"id"`
Key string `json:"key"`
Doc struct{} `json:"doc"`
}

type missingIDStruct struct {
Rows []int `json:"rows"`
Key string `json:"key"`
Doc struct{} `json:"doc"`
}

type missingKeyStruct struct {
Rows []int `json:"rows"`
ID string `json:"id"`
Doc struct{} `json:"doc"`
}

type missingIDTagStruct struct {
Rows []int `json:"rows"`
ID string
Key string `json:"key"`
Doc struct{} `json:"doc"`
}

type missingKeyTagStruct struct {
Rows []int `json:"rows"`
ID string `json:"id"`
Key string
Doc struct{} `json:"doc"`
}

type validDocStruct struct {
Rows []int `json:"rows"`
ID string `json:"id"`
Key string `json:"key"`
Doc struct{} `json:"doc"`
IncludeDocs bool `json:"include_docs"`
}

type missingDocTagStruct struct {
Rows []int `json:"rows"`
ID string `json:"id"`
Key string `json:"key"`
Doc struct{} `json:"dock"`
IncludeDocs bool
}

0 comments on commit 1ab0402

Please sign in to comment.