-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add support for Google Docs tabs #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,12 +24,55 @@ func NewClient(ctx context.Context, httpClient *http.Client) (*Client, error) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return &Client{service: service}, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // FetchDocument retrieves a Google Docs document by its ID. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // FetchDocument retrieves a Google Docs document by its ID with all tabs included. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (c *Client) FetchDocument(docID string) (*docs.Document, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| doc, err := c.service.Documents.Get(docID).Do() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| doc, err := c.service.Documents.Get(docID).IncludeTabsContent(true).Do() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("unable to retrieve document: %w\n\nThis could mean:\n1. The document is private and you don't have permission\n2. The document doesn't exist\n3. The document ID is incorrect", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return doc, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // FindTab searches for a tab by ID in the document's tab tree. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Returns nil if the tab is not found. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func FindTab(doc *docs.Document, tabID string) *docs.Tab { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if doc == nil || doc.Tabs == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, tab := range doc.Tabs { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if found := findTabRecursive(tab, tabID); found != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return found | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // findTabRecursive recursively searches for a tab by ID. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func findTabRecursive(tab *docs.Tab, tabID string) *docs.Tab { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if tab == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if tab.TabProperties != nil && tab.TabProperties.TabId == tabID { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return tab | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, child := range tab.ChildTabs { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if found := findTabRecursive(child, tabID); found != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return found | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential nil pointer dereference if a child tab is nil. The function accesses 🛡️ Suggested defensive fix // findTabRecursive recursively searches for a tab by ID.
func findTabRecursive(tab *docs.Tab, tabID string) *docs.Tab {
+ if tab == nil {
+ return nil
+ }
if tab.TabProperties != nil && tab.TabProperties.TabId == tabID {
return tab
}
for _, child := range tab.ChildTabs {
if found := findTabRecursive(child, tabID); found != nil {
return found
}
}
return nil
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // GetFirstTab returns the first tab in the document. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Returns nil if the document has no tabs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func GetFirstTab(doc *docs.Document) *docs.Tab { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if doc == nil || doc.Tabs == nil || len(doc.Tabs) == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return doc.Tabs[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| package gdocs | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "google.golang.org/api/docs/v1" | ||
| ) | ||
|
|
||
| func TestFindTab(t *testing.T) { | ||
| // Create a mock document with nested tabs | ||
| doc := &docs.Document{ | ||
| Title: "Test Document", | ||
| Tabs: []*docs.Tab{ | ||
| { | ||
| TabProperties: &docs.TabProperties{ | ||
| TabId: "t.tab1", | ||
| Title: "Tab 1", | ||
| }, | ||
| DocumentTab: &docs.DocumentTab{ | ||
| Body: &docs.Body{}, | ||
| }, | ||
| ChildTabs: []*docs.Tab{ | ||
| { | ||
| TabProperties: &docs.TabProperties{ | ||
| TabId: "t.tab1child1", | ||
| Title: "Tab 1 Child 1", | ||
| }, | ||
| DocumentTab: &docs.DocumentTab{ | ||
| Body: &docs.Body{}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| TabProperties: &docs.TabProperties{ | ||
| TabId: "t.tab2", | ||
| Title: "Tab 2", | ||
| }, | ||
| DocumentTab: &docs.DocumentTab{ | ||
| Body: &docs.Body{}, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| tabID string | ||
| wantTitle string | ||
| wantNil bool | ||
| }{ | ||
| { | ||
| name: "find top-level tab", | ||
| tabID: "t.tab1", | ||
| wantTitle: "Tab 1", | ||
| wantNil: false, | ||
| }, | ||
| { | ||
| name: "find second top-level tab", | ||
| tabID: "t.tab2", | ||
| wantTitle: "Tab 2", | ||
| wantNil: false, | ||
| }, | ||
| { | ||
| name: "find nested child tab", | ||
| tabID: "t.tab1child1", | ||
| wantTitle: "Tab 1 Child 1", | ||
| wantNil: false, | ||
| }, | ||
| { | ||
| name: "tab not found", | ||
| tabID: "t.nonexistent", | ||
| wantNil: true, | ||
| }, | ||
| { | ||
| name: "empty tab ID", | ||
| tabID: "", | ||
| wantNil: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got := FindTab(doc, tt.tabID) | ||
| if tt.wantNil { | ||
| if got != nil { | ||
| t.Errorf("FindTab() = %v, want nil", got) | ||
| } | ||
| return | ||
| } | ||
| if got == nil { | ||
| t.Errorf("FindTab() = nil, want tab with title %q", tt.wantTitle) | ||
| return | ||
| } | ||
| if got.TabProperties.Title != tt.wantTitle { | ||
| t.Errorf("FindTab() title = %q, want %q", got.TabProperties.Title, tt.wantTitle) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestFindTab_NilTabs(t *testing.T) { | ||
| doc := &docs.Document{ | ||
| Title: "Test Document", | ||
| Tabs: nil, | ||
| } | ||
|
|
||
| got := FindTab(doc, "t.any") | ||
| if got != nil { | ||
| t.Errorf("FindTab() with nil tabs = %v, want nil", got) | ||
| } | ||
| } | ||
|
|
||
| func TestFindTab_NilDocument(t *testing.T) { | ||
| got := FindTab(nil, "t.any") | ||
| if got != nil { | ||
| t.Errorf("FindTab() with nil document = %v, want nil", got) | ||
| } | ||
| } | ||
|
|
||
| func TestGetFirstTab_NilDocument(t *testing.T) { | ||
| got := GetFirstTab(nil) | ||
| if got != nil { | ||
| t.Errorf("GetFirstTab() with nil document = %v, want nil", got) | ||
| } | ||
| } | ||
|
|
||
| func TestGetFirstTab(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| doc *docs.Document | ||
| wantTitle string | ||
| wantNil bool | ||
| }{ | ||
| { | ||
| name: "document with tabs", | ||
| doc: &docs.Document{ | ||
| Tabs: []*docs.Tab{ | ||
| { | ||
| TabProperties: &docs.TabProperties{ | ||
| TabId: "t.first", | ||
| Title: "First Tab", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| wantTitle: "First Tab", | ||
| wantNil: false, | ||
| }, | ||
| { | ||
| name: "document with no tabs", | ||
| doc: &docs.Document{ | ||
| Tabs: []*docs.Tab{}, | ||
| }, | ||
| wantNil: true, | ||
| }, | ||
| { | ||
| name: "document with nil tabs", | ||
| doc: &docs.Document{ | ||
| Tabs: nil, | ||
| }, | ||
| wantNil: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got := GetFirstTab(tt.doc) | ||
| if tt.wantNil { | ||
| if got != nil { | ||
| t.Errorf("GetFirstTab() = %v, want nil", got) | ||
| } | ||
| return | ||
| } | ||
| if got == nil { | ||
| t.Errorf("GetFirstTab() = nil, want tab with title %q", tt.wantTitle) | ||
| return | ||
| } | ||
| if got.TabProperties.Title != tt.wantTitle { | ||
| t.Errorf("GetFirstTab() title = %q, want %q", got.TabProperties.Title, tt.wantTitle) | ||
| } | ||
| }) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.