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.go — BuildFromRequest():
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
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.go — BuildFromRequest() |
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.py — find_by_uid() |
Real-world workaround: hardcoded &limit=1000 |
| 4 |
ogc-client-CSAPI_2#167 |
Companion client-side issue |
Finding
QueryParams.BuildFromRequest()hardcodesLimit: 10as 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=Nparameter, only the first 10 resources are returned. The response includesrel=nextpagination 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.go—BuildFromRequest():Scenario:
Impact: Combined with
?uid=being ignored (#7), this was particularly impactful during the publisher fleet migration. Thefind_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:Comparison with other OGC API implementations:
limitWhile 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-gorepository. TheOS4CSAPIfork is currently synced with upstream (mainbranch is up to date).Conclusion: This code is upstream.
Files to Modify
internal/model/query_params/query_params.goLimit: 10toLimit: 100(or make configurable)Proposed Solutions
Option A: Increase default to 100 (Recommended)
Pros: Single-line change; matches SensorHub default; backward compatible (clients passing explicit
?limit=Nare unaffected); greatly improves usability for typical workloadsCons: Higher memory usage per request for servers with many resources; opinionated
Effort: Small | Risk: Low
Option B: Make default configurable via
config.yamlPros: 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
limitparsing logic (it correctly reads explicit?limit=N)BuildPagintationLinks()Acceptance Criteria
GET /systems(no?limit=) returns up to 100 results (or configured default)GET /systems?limit=10still returns exactly 10 results (explicit override works)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
Key constraints:
Ownership-Specific Constraints
If Upstream:
References
internal/model/query_params/query_params.go—BuildFromRequest()Limit: 10limitis optional with server-defined defaultpublishers/bootstrap_helpers.py—find_by_uid()&limit=1000