Files
onspring-api-sdk-go/records.go
T

433 lines
12 KiB
Go

package onspring
import (
"context"
"fmt"
"iter"
"net/http"
"strconv"
"strings"
)
const (
recordsPath = "/records"
)
// RecordsEndpoint provides access to records in an Onspring instance.
type RecordsEndpoint struct {
client *Client
}
// Record represents an Onspring record.
type Record struct {
AppId int `json:"appId"`
RecordId int `json:"recordId"`
FieldData []RecordFieldValue `json:"fieldData"`
}
// RecordFieldValue represents a field value within a record.
type RecordFieldValue struct {
Type string `json:"type"`
FieldId int `json:"fieldId"`
Value any `json:"value"`
}
// RecordBatch represents a batch of Onspring records.
type RecordBatch struct {
Count int `json:"count"`
Items []Record `json:"items"`
}
// RecordOption is a functional option for configuring record requests.
type RecordOption func(*recordRequest)
type recordRequest struct {
FieldIds []int
DataFormat string
PagingRequest PagingRequest
}
func (r *recordRequest) ToParams() map[string]string {
params := r.PagingRequest.ToParams()
if len(r.FieldIds) > 0 {
ids := make([]string, len(r.FieldIds))
for i, id := range r.FieldIds {
ids[i] = strconv.Itoa(id)
}
params["fieldIds"] = strings.Join(ids, ",")
}
if r.DataFormat != "" {
params["dataFormat"] = r.DataFormat
}
return params
}
func (r *recordRequest) ToQueryParams() map[string]string {
params := map[string]string{}
if len(r.FieldIds) > 0 {
ids := make([]string, len(r.FieldIds))
for i, id := range r.FieldIds {
ids[i] = strconv.Itoa(id)
}
params["fieldIds"] = strings.Join(ids, ",")
}
if r.DataFormat != "" {
params["dataFormat"] = r.DataFormat
}
return params
}
func createRecordRequest(opts []RecordOption) *recordRequest {
r := &recordRequest{
PagingRequest: PagingRequest{PageNumber: 1, PageSize: 50},
}
for _, opt := range opts {
opt(r)
}
return r
}
// WithFieldIds sets the field identifiers to include in the record response.
func WithFieldIds(ids []int) RecordOption {
return func(r *recordRequest) {
r.FieldIds = ids
}
}
// WithRecordDataFormat sets the data format for the record response.
// Valid values are "Raw" and "Formatted".
func WithRecordDataFormat(format string) RecordOption {
return func(r *recordRequest) {
r.DataFormat = format
}
}
// WithPaging applies paging options to a record request.
func WithPaging(opts ...PagingOption) RecordOption {
return func(r *recordRequest) {
for _, opt := range opts {
opt(&r.PagingRequest)
}
}
}
// GetManyRecordsRequest represents a request to get a batch of records.
type GetManyRecordsRequest struct {
AppId int `json:"appId"`
RecordIds []int `json:"recordIds"`
FieldIds []int `json:"fieldIds,omitempty"`
DataFormat string `json:"dataFormat,omitempty"`
}
// QueryRecordsRequest represents a request to query records.
type QueryRecordsRequest struct {
AppId int `json:"appId"`
Filter string `json:"filter"`
FieldIds []int `json:"fieldIds,omitempty"`
DataFormat string `json:"dataFormat,omitempty"`
}
// SaveRecordRequest represents a request to save a record.
type SaveRecordRequest struct {
AppId int `json:"appId"`
RecordId *int `json:"recordId,omitempty"`
Fields map[string]any `json:"fields"`
}
// SaveRecordResponse represents the response for saving a record.
type SaveRecordResponse struct {
Id int `json:"id"`
Warnings []string `json:"warnings"`
}
// DeleteManyRecordsRequest represents a request to delete a batch of records.
type DeleteManyRecordsRequest struct {
AppId int `json:"appId"`
RecordIds []int `json:"recordIds"`
}
// Get retrieves a record from the Onspring API.
//
// Parameters:
// - ctx: The context for the request
// - appId: The id of the app
// - recordId: The id of the record to retrieve
// - opts: Optional record configuration functions (e.g., WithFieldIds, WithRecordDataFormat)
//
// Returns:
// - Record: A record
// - error: An error if the request fails
func (rc *RecordsEndpoint) Get(ctx context.Context, appId, recordId int, opts ...RecordOption) (Record, error) {
recordReq := createRecordRequest(opts)
path := fmt.Sprintf("%s/appId/%d/recordId/%d", recordsPath, appId, recordId)
req, requestCreationErr := rc.client.newRequest(ctx, http.MethodGet, path, recordReq.ToQueryParams(), nil)
var record Record
if requestCreationErr != nil {
return record, requestCreationErr
}
responseErr := rc.client.doWithJsonResponse(req, &record)
if responseErr != nil {
return record, responseErr
}
return record, nil
}
// List retrieves a paginated list of records for an app from the Onspring API.
//
// Parameters:
// - ctx: The context for the request
// - appId: The id of the app to retrieve records for
// - opts: Optional record configuration functions (e.g., WithFieldIds, WithRecordDataFormat, WithPaging)
//
// Returns:
// - Page[Record]: A page of records with pagination metadata
// - error: An error if the request fails
func (rc *RecordsEndpoint) List(ctx context.Context, appId int, opts ...RecordOption) (Page[Record], error) {
recordReq := createRecordRequest(opts)
path := fmt.Sprintf("%s/appId/%d", recordsPath, appId)
req, requestCreationErr := rc.client.newRequest(ctx, http.MethodGet, path, recordReq.ToParams(), nil)
var page Page[Record]
if requestCreationErr != nil {
return page, requestCreationErr
}
responseErr := rc.client.doWithJsonResponse(req, &page)
if responseErr != nil {
return page, responseErr
}
return page, nil
}
// ListAll returns an iterator that yields all Records for an app across all pages.
// It automatically handles pagination by making sequential calls to List
// until all items have been retrieved or the caller stops the iteration.
//
// Parameters:
// - ctx: The context for the request
// - appId: The id of the app to retrieve records for
// - opts: Optional record configuration functions (e.g., WithFieldIds, WithRecordDataFormat, WithPaging)
//
// Returns:
// - iter.Seq2[Record, error]: An iterator yielding:
// - Record: The individual record.
// - error: An error if a specific page request fails during iteration.
func (rc *RecordsEndpoint) ListAll(ctx context.Context, appId int, opts ...RecordOption) iter.Seq2[Record, error] {
return func(yield func(Record, error) bool) {
recordReq := createRecordRequest(opts)
for {
page, err := rc.List(
ctx,
appId,
WithFieldIds(recordReq.FieldIds),
WithRecordDataFormat(recordReq.DataFormat),
WithPaging(ForPageNumber(recordReq.PagingRequest.PageNumber), WithPageSize(recordReq.PagingRequest.PageSize)),
)
if err != nil {
yield(Record{}, err)
return
}
for _, item := range page.Items {
if !yield(item, nil) {
return
}
}
if page.PageNumber >= page.TotalPages {
break
}
recordReq.PagingRequest.PageNumber++
}
}
}
// GetMany retrieves a batch of records from the Onspring API.
//
// Parameters:
// - ctx: The context for the request
// - request: The batch get request containing app id, record ids, and optional field ids/data format
//
// Returns:
// - RecordBatch: A batch of records
// - error: An error if the request fails
func (rc *RecordsEndpoint) GetMany(ctx context.Context, request GetManyRecordsRequest) (RecordBatch, error) {
path := fmt.Sprintf("%s/batch-get", recordsPath)
req, requestCreationErr := rc.client.newRequest(ctx, http.MethodPost, path, nil, request)
var batch RecordBatch
if requestCreationErr != nil {
return batch, requestCreationErr
}
responseErr := rc.client.doWithJsonResponse(req, &batch)
if responseErr != nil {
return batch, responseErr
}
return batch, nil
}
// Query queries records from the Onspring API.
//
// Parameters:
// - ctx: The context for the request
// - request: The query request containing app id, filter, and optional field ids/data format
// - pagingOpts: Optional paging configuration functions (e.g., ForPageNumber, WithPageSize)
//
// Returns:
// - Page[Record]: A page of records with pagination metadata
// - error: An error if the request fails
func (rc *RecordsEndpoint) Query(ctx context.Context, request QueryRecordsRequest, pagingOpts ...PagingOption) (Page[Record], error) {
pagingRequest := createPagingRequest(pagingOpts)
path := fmt.Sprintf("%s/query", recordsPath)
req, requestCreationErr := rc.client.newRequest(ctx, http.MethodPost, path, pagingRequest.ToParams(), request)
var page Page[Record]
if requestCreationErr != nil {
return page, requestCreationErr
}
responseErr := rc.client.doWithJsonResponse(req, &page)
if responseErr != nil {
return page, responseErr
}
return page, nil
}
// QueryAll returns an iterator that yields all Records matching a query across all pages.
// It automatically handles pagination by making sequential calls to Query
// until all items have been retrieved or the caller stops the iteration.
//
// Parameters:
// - ctx: The context for the request
// - request: The query request containing app id, filter, and optional field ids/data format
// - pagingOpts: Optional paging configuration functions (e.g., ForPageNumber, WithPageSize)
//
// Returns:
// - iter.Seq2[Record, error]: An iterator yielding:
// - Record: The individual record.
// - error: An error if a specific page request fails during iteration.
func (rc *RecordsEndpoint) QueryAll(ctx context.Context, request QueryRecordsRequest, pagingOpts ...PagingOption) iter.Seq2[Record, error] {
return func(yield func(Record, error) bool) {
pagingRequest := createPagingRequest(pagingOpts)
for {
page, err := rc.Query(ctx, request, ForPageNumber(pagingRequest.PageNumber), WithPageSize(pagingRequest.PageSize))
if err != nil {
yield(Record{}, err)
return
}
for _, item := range page.Items {
if !yield(item, nil) {
return
}
}
if page.PageNumber >= page.TotalPages {
break
}
pagingRequest.PageNumber++
}
}
}
// Save creates or updates a record in the Onspring API.
//
// Parameters:
// - ctx: The context for the request
// - request: The save request containing app id, optional record id, and field values
//
// Returns:
// - SaveRecordResponse: The response containing the saved record's id and any warnings
// - error: An error if the request fails
func (rc *RecordsEndpoint) Save(ctx context.Context, request SaveRecordRequest) (SaveRecordResponse, error) {
req, requestCreationErr := rc.client.newRequest(ctx, http.MethodPut, recordsPath, nil, request)
var response SaveRecordResponse
if requestCreationErr != nil {
return response, requestCreationErr
}
responseErr := rc.client.doWithJsonResponse(req, &response)
if responseErr != nil {
return response, responseErr
}
return response, nil
}
// Delete removes a record from the Onspring API.
//
// Parameters:
// - ctx: The context for the request
// - appId: The id of the app
// - recordId: The id of the record to delete
//
// Returns:
// - error: An error if the request fails
func (rc *RecordsEndpoint) Delete(ctx context.Context, appId, recordId int) error {
path := fmt.Sprintf("%s/appId/%d/recordId/%d", recordsPath, appId, recordId)
req, requestCreationErr := rc.client.newRequest(ctx, http.MethodDelete, path, nil, nil)
if requestCreationErr != nil {
return requestCreationErr
}
return rc.client.do(req)
}
// DeleteMany removes a batch of records from the Onspring API.
//
// Parameters:
// - ctx: The context for the request
// - request: The batch delete request containing app id and record ids
//
// Returns:
// - error: An error if the request fails
func (rc *RecordsEndpoint) DeleteMany(ctx context.Context, request DeleteManyRecordsRequest) error {
path := fmt.Sprintf("%s/batch-delete", recordsPath)
req, requestCreationErr := rc.client.newRequest(ctx, http.MethodPost, path, nil, request)
if requestCreationErr != nil {
return requestCreationErr
}
return rc.client.do(req)
}