Skip to content

Default pagination limit of 10 is unusually low — causes silent result truncation for typical workloads #9

@Sam-Bolling

Description

@Sam-Bolling

Finding

QueryParams.BuildFromRequest() hardcodes Limit: 10 as the default page size, causing silent result truncation for any workload with more than 10 resources of a given type.

Review Source: Live integration testing against the Go CSAPI server during OSHConnect-Python publisher fleet migration to the Go server.
Severity: P3-Minor
Category: API Design
Ownership: Upstream (SomethingCreativeStudios/connected-systems-go)


Problem Statement

The default pagination limit of 10 is significantly lower than other OGC API implementations. When a client requests a collection endpoint without an explicit ?limit=N parameter, only the first 10 resources are returned. The response includes rel=next pagination links, so it's technically conformant — but the low default means that most simple client integrations (which don't implement pagination following) silently miss data.

Affected code — internal/model/query_params/query_params.goBuildFromRequest():

func (QueryParams) BuildFromRequest(r *http.Request) *QueryParams {
    params := &QueryParams{
        Limit:  10,   // ← Hardcoded default — unusually low
        Offset: 0,
    }

    if limit := r.URL.Query().Get("limit"); limit != "" {
        if val, err := strconv.Atoi(limit); err == nil {
            params.Limit = val
        }
    }
    // ...
}

Scenario:

# Server has 37 systems registered
GET /systems
# Expected: reasonable page of results (50-100 is typical for OGC APIs)
# Actual: only 10 systems returned, with pagination links to subsequent pages

# Same issue affects all resource types:
GET /datastreams     # 58 datastreams, only 10 returned
GET /deployments     # 58 deployments, only 10 returned
GET /observations    # hundreds of observations, only 10 returned

Impact: Combined with ?uid= being ignored (#7), this was particularly impactful during the publisher fleet migration. The find_by_uid() helper fetches a collection and iterates client-side to find a matching UID. With a default limit of 10, any resource beyond the first 10 was unfindable. Every publisher bootstrap script had to be updated with ?limit=1000:

# Workaround in bootstrap_helpers.py — explicit large limit
resp = session.get(f"{url}?limit=1000")

Comparison with other OGC API implementations:

Server Default limit
SensorHub 100
pygeoapi 10
ldproxy 10
Go CSAPI 10

While 10 is not uncommon, the combination of limit=10 + missing ?uid= filter makes this server significantly harder to work with than others. Once #7 is fixed, this becomes a lower-priority cosmetic issue.


Ownership Verification

This code is pre-existing in the upstream SomethingCreativeStudios/connected-systems-go repository. The OS4CSAPI fork is currently synced with upstream (main branch is up to date).

Conclusion: This code is upstream.

Files to Modify

File Action Est. Lines Purpose
internal/model/query_params/query_params.go Modify ~3 Change Limit: 10 to Limit: 100 (or make configurable)

Proposed Solutions

Option A: Increase default to 100 (Recommended)

func (QueryParams) BuildFromRequest(r *http.Request) *QueryParams {
    params := &QueryParams{
        Limit:  100,  // Changed from 10 to 100
        Offset: 0,
    }
    // ...
}

Pros: Single-line change; matches SensorHub default; backward compatible (clients passing explicit ?limit=N are unaffected); greatly improves usability for typical workloads
Cons: Higher memory usage per request for servers with many resources; opinionated
Effort: Small | Risk: Low

Option B: Make default configurable via config.yaml

// internal/config/config.go
type APIConfig struct {
    BaseURL      string `yaml:"base_url"`
    DefaultLimit int    `yaml:"default_limit"` // NEW
}

// internal/model/query_params/query_params.go
// Pass config to BuildFromRequest, or use a package-level default
var DefaultLimit = 100

func (QueryParams) BuildFromRequest(r *http.Request) *QueryParams {
    params := &QueryParams{
        Limit:  DefaultLimit,
        Offset: 0,
    }
    // ...
}

Pros: Deployers can tune to their workload; allows different defaults for different deployment contexts
Cons: Larger diff; requires threading config through to query param construction; adds deployment complexity
Effort: Medium | Risk: Low

Scope — What NOT to Touch

  • ❌ Do NOT modify files outside the "Files to Modify" table above
  • ❌ Do NOT refactor adjacent code that isn't part of this finding
  • ❌ Do NOT change the limit parsing logic (it correctly reads explicit ?limit=N)
  • ❌ Do NOT add maximum limit enforcement — that's a separate concern
  • ❌ Do NOT modify pagination link generation in BuildPagintationLinks()

Acceptance Criteria

  • GET /systems (no ?limit=) returns up to 100 results (or configured default)
  • GET /systems?limit=10 still returns exactly 10 results (explicit override works)
  • Pagination links are correct for the new default
  • Existing tests still pass (make test)

Dependencies

Blocked by: Nothing
Blocks: Nothing
Related: #7?uid= filter ignored (fixing #7 reduces the impact of this issue significantly); #8 — Subdeployments hidden (more resources in listing increases importance of reasonable defaults); ogc-client-CSAPI_2#167 — Client library also lacks a default limit (companion client-side fix)


Operational Constraints

⚠️ MANDATORY: Before starting work on this issue, review docs/governance/AI_OPERATIONAL_CONSTRAINTS.md if available.

Key constraints:

  • Precedence: OGC specifications → AI Collaboration Agreement → This issue description → Existing code → Conversational context
  • No scope expansion: Fix the finding, nothing more
  • Minimal diffs: Prefer the smallest change that satisfies the acceptance criteria
  • Ask when unclear: If intent is ambiguous, stop and ask for clarification
  • Respect ownership: This code is upstream — coordinate with the maintainer if contributing back

Ownership-Specific Constraints

If Upstream:

  • Track the issue for potential future contribution or discussion with the maintainer
  • If the fix is trivial and clearly beneficial, note in the issue that it could be offered as a separate upstream PR

References

# Document What It Provides
1 internal/model/query_params/query_params.goBuildFromRequest() Root cause — hardcoded Limit: 10
2 OGC 23-001 §7.6 — Limit parameter Spec says limit is optional with server-defined default
3 OSHConnect-Python publishers/bootstrap_helpers.pyfind_by_uid() Real-world workaround: hardcoded &limit=1000
4 ogc-client-CSAPI_2#167 Companion client-side issue

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions