// todo/service.go
package todo
import (
"context"
"fmt"
"sync"
contract "github.com/go-mizu/mizu/contract/v2"
)
// Service implements todo.API
type Service struct {
mu sync.RWMutex
todos map[string]*Todo
nextID int
}
// Compile-time check
var _ API = (*Service)(nil)
func NewService() *Service {
return &Service{todos: make(map[string]*Todo)}
}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
if in.Title == "" {
return nil, contract.ErrInvalidArgument("title is required")
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
todo := &Todo{
ID: fmt.Sprintf("todo_%d", s.nextID),
Title: in.Title,
}
s.todos[todo.ID] = todo
return todo, nil
}
func (s *Service) List(ctx context.Context) (*ListOutput, error) {
s.mu.RLock()
defer s.mu.RUnlock()
items := make([]*Todo, 0, len(s.todos))
for _, t := range s.todos {
items = append(items, t)
}
return &ListOutput{Items: items, Count: len(items)}, nil
}
func (s *Service) Get(ctx context.Context, in *GetInput) (*Todo, error) {
s.mu.RLock()
defer s.mu.RUnlock()
todo, ok := s.todos[in.ID]
if !ok {
return nil, contract.ErrNotFound("todo not found")
}
return todo, nil
}
func (s *Service) Update(ctx context.Context, in *UpdateInput) (*Todo, error) {
s.mu.Lock()
defer s.mu.Unlock()
todo, ok := s.todos[in.ID]
if !ok {
return nil, contract.ErrNotFound("todo not found")
}
todo.Title = in.Title
todo.Completed = in.Completed
return todo, nil
}
func (s *Service) Delete(ctx context.Context, in *DeleteInput) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.todos[in.ID]; !ok {
return contract.ErrNotFound("todo not found")
}
delete(s.todos, in.ID)
return nil
}