diff --git a/internal/api/documents.go b/internal/api/documents.go index 9fe959921..fe98877aa 100644 --- a/internal/api/documents.go +++ b/internal/api/documents.go @@ -40,13 +40,6 @@ type DocumentPatchRequest struct { TargetVersion string `json:"targetVersion,omitempty"` } -var ( - documentsResourceURLPathRE = regexp.MustCompile( - `^\/api\/v1\/documents\/([0-9A-Za-z_\-]+)$`) - documentsResourceRelatedResourcesURLPathRE = regexp.MustCompile( - `^\/api\/v1\/documents\/([0-9A-Za-z_\-]+)\/related-resources$`) -) - func DocumentHandler( cfg *config.Config, l hclog.Logger, @@ -57,7 +50,8 @@ func DocumentHandler( return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Parse document ID from the URL path. - docID, isRelatedResourcesRequest, err := parseDocumentsURLPath(r.URL.Path) + docID, isRelatedResourcesRequest, err := parseDocumentsURLPath( + r.URL.Path, "documents") if err != nil { l.Error("error parsing documents URL path", "error", err, @@ -491,28 +485,38 @@ func updateRecentlyViewedDocs( // parseDocumentsURLPath parses the document ID from a documents API URL path // and determines if it is a related resources request. -func parseDocumentsURLPath(path string) ( +// resourceType should be "documents" or "drafts", as appropriate. +func parseDocumentsURLPath(path, resourceType string) ( docID string, isRelatedResourcesRequest bool, err error, ) { + resourceURLPathRE := regexp.MustCompile( + fmt.Sprintf( + `^\/api\/v1\/%s\/([0-9A-Za-z_\-]+)$`, + resourceType)) + resourceRelatedResourcesURLPathRE := regexp.MustCompile( + fmt.Sprintf( + `^\/api\/v1\/%s\/([0-9A-Za-z_\-]+)\/related-resources$`, + resourceType)) + switch { - case documentsResourceURLPathRE.MatchString(path): - matches := documentsResourceURLPathRE.FindStringSubmatch(path) + case resourceURLPathRE.MatchString(path): + matches := resourceURLPathRE.FindStringSubmatch(path) if len(matches) != 2 { return "", false, fmt.Errorf( - "wrong number of string submatches for documents resource URL path") + "wrong number of string submatches for resource URL path") } return matches[1], false, nil - case documentsResourceRelatedResourcesURLPathRE.MatchString(path): - matches := documentsResourceRelatedResourcesURLPathRE. + case resourceRelatedResourcesURLPathRE.MatchString(path): + matches := resourceRelatedResourcesURLPathRE. FindStringSubmatch(path) if len(matches) != 2 { return "", true, fmt.Errorf( - "wrong number of string submatches for documents resource related resources URL path") + "wrong number of string submatches for resource related resources URL path") } return matches[1], true, nil diff --git a/internal/api/documents_related_resources.go b/internal/api/documents_related_resources.go index 0dfd1dae3..f8a3af6b4 100644 --- a/internal/api/documents_related_resources.go +++ b/internal/api/documents_related_resources.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" - "github.com/algolia/algoliasearch-client-go/v3/algolia/errs" "github.com/hashicorp-forge/hermes/pkg/algolia" hcd "github.com/hashicorp-forge/hermes/pkg/hashicorpdocs" "github.com/hashicorp-forge/hermes/pkg/models" @@ -238,18 +237,15 @@ func documentsResourceRelatedResourcesHandler( } } -func getDocumentFromAlgolia(docID string, algo *algolia.Client) (hcd.Doc, error) { +// getDocumentFromAlgolia gets a document object from Algolia. +func getDocumentFromAlgolia( + docID string, algo *algolia.Client) (hcd.Doc, error) { + // Get base document object from Algolia so we can determine the doc type. baseDocObj := &hcd.BaseDoc{} - err := algo.Docs.GetObject(docID, &baseDocObj) - if err != nil { - // Handle 404 from Algolia and only log a warning. - if _, is404 := errs.IsAlgoliaErrWithCode(err, 404); is404 { - return nil, fmt.Errorf("base document object not found") - } else { - return nil, fmt.Errorf( - "error requesting base document object from Algolia: %w", err) - } + if err := algo.Docs.GetObject(docID, &baseDocObj); err != nil { + return nil, fmt.Errorf( + "error retrieving base document object from Algolia: %w", err) } // Create new document object of the proper doc type. @@ -259,8 +255,7 @@ func getDocumentFromAlgolia(docID string, algo *algolia.Client) (hcd.Doc, error) } // Get document object from Algolia. - err = algo.Docs.GetObject(docID, &docObj) - if err != nil { + if err := algo.Docs.GetObject(docID, &docObj); err != nil { return nil, fmt.Errorf("error retrieving document object from Algolia") } diff --git a/internal/api/documents_test.go b/internal/api/documents_test.go index 135796982..be23b9a04 100644 --- a/internal/api/documents_test.go +++ b/internal/api/documents_test.go @@ -9,33 +9,49 @@ import ( func TestParseDocumentsURLPath(t *testing.T) { cases := map[string]struct { url string + resourceType string wantDocID string wantRelatedResourceRequest bool shouldErr bool }{ "good document resource URL": { - url: "/api/v1/documents/doc123", - wantDocID: "doc123", + url: "/api/v1/documents/doc123", + resourceType: "documents", + wantDocID: "doc123", }, "good document resource URL with related resources": { url: "/api/v1/documents/doc123/related-resources", + resourceType: "documents", + wantDocID: "doc123", + wantRelatedResourceRequest: true, + }, + "good draft resource URL": { + url: "/api/v1/drafts/doc123", + resourceType: "drafts", + wantDocID: "doc123", + }, + "good draft resource URL with related resources": { + url: "/api/v1/drafts/doc123/related-resources", + resourceType: "drafts", wantDocID: "doc123", wantRelatedResourceRequest: true, }, "extra frontslash after related-resources": { - url: "/api/v1/documents/doc123/related-resources/", - shouldErr: true, + url: "/api/v1/documents/doc123/related-resources/", + resourceType: "documents", + shouldErr: true, }, "no document resource ID": { - url: "/api/v1/documents/", - shouldErr: true, + url: "/api/v1/documents/", + resourceType: "documents", + shouldErr: true, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { assert := assert.New(t) - docID, rrReq, err := parseDocumentsURLPath(c.url) + docID, rrReq, err := parseDocumentsURLPath(c.url, c.resourceType) if c.shouldErr { assert.Error(err) diff --git a/internal/api/drafts.go b/internal/api/drafts.go index f5a346471..0703842ff 100644 --- a/internal/api/drafts.go +++ b/internal/api/drafts.go @@ -439,14 +439,16 @@ func DraftsDocumentHandler( db *gorm.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Get document ID from URL path - docId, err := parseURLPath(r.URL.Path, "/api/v1/drafts") + // Parse document ID from the URL path. + docId, isRelatedResourcesRequest, err := parseDocumentsURLPath( + r.URL.Path, "drafts") if err != nil { - l.Error("error requesting document draft from algolia", + l.Error("error parsing drafts URL path", "error", err, "path", r.URL.Path, + "method", r.Method, ) - http.Error(w, "Error requesting document draft", http.StatusInternalServerError) + http.Error(w, "Bad request", http.StatusBadRequest) return } @@ -519,6 +521,13 @@ func DraftsDocumentHandler( return } + // Pass request off to the documents related resources handler if + // appropriate. + if isRelatedResourcesRequest { + documentsResourceRelatedResourcesHandler(w, r, docId, docObj, l, ar, db) + return + } + switch r.Method { case "GET": now := time.Now()