Skip to content

docs: make every example compile against the SDK the docs describe - #103

Merged
JosteinGj merged 14 commits into
masterfrom
fix/docs-match-sdk-api
Sep 29, 2026
Merged

JosteinGj merged 14 commits into
masterfrom
fix/docs-match-sdk-api

Conversation

@JosteinGj

@JosteinGj JosteinGj commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

The three Doc tutorials jobs have been failing on every run in recent history. The
harness was right: the examples had drifted from the SDK across roughly a hundred
page/language pairs — 245 distinct compile errors from nine root causes — plus one
real harness bug and one stale plan selection that was hiding a defect.

Job Before After
Plans match the docs 7 failed, 340 passed 440 passed
Docs match the SDK's API 7 failed, 215 passed 222 passed
Java and Rust examples compile 100 failed, 58 passed 159 passed

Every claim was checked against source — datahub-java-sdk/datahub-api-model and
dataplatform-rust-sdk at main — rather than inferred from the error text. The local
run was first made to reproduce CI error-for-error (100 failed, 58 passed) so the
before/after means something.

What was wrong

Java. DataWrapper.getItems() returned Collection, so 27 .get(0) reads never
compiled. That envelope is now a List (datahub-platform#141, merged), so those reads stand as
written, and the eight pages that had worked around it with .iterator().next() were brought into
line — 35 sites reading getItems().get(0), the way the Python and Rust tabs index. Timeseries has no static of() and NodeModel's setters are Lombok-generated
and return void, so neither the factory nor the 19 fluent chains after it existed.
ResourceForm and ResourceSearch are gone — resources are created from Resource, and
SearchBody<F>'s own javadoc names ResourceSearch as one of the four classes it folded
together. ResourceNetwork.nodes() is a Set<NodeModel>, so 16 traversals mapped
Resource::getExternalId over a stream that never holds one.

Rust. Event::new takes all three required fields, and the struct's r#type and
event_time are plain values rather than Options — the field's doc comment says why —
so 27 pages passed the external id alone and then assigned Some(...) to both.
Node::external_id is a method. FileUpload::new_with_destination_path answers
io::Result. Datapoint.value is Option<f64> and .timestamp a DateTime<Utc>, so
five pages parsed strings that were never strings. BasicEventFilter is now EventFilter
(the criteria) and EventFilterForm (the request body). The tutorial's step blocks showed
none of the use lines they needed, so a reader following the steps could not compile them.

Python. events.filter is keyword-only, and there is no BasicEventFilter; seven pages
used the Rust shape. docs/reference/events.md already described the Python call correctly.

Two that are not doc fixes

A harness bug (test(compile): a reader's value cannot fail the call it is passed to).
javac resolves neither an overload nor a target type through an argument it could not
resolve, so an unresolved reader-supplied name is reported twice: as the name, and again as
a type error on the enclosing call — a different line, which is why the existing per-line
suppression never caught it. Five pages were failing on calls that compile. The stubbed pass
now retracts as well as adds, and narrowly: typing errors only, only in units that have
placeholders, only where the stubbed pass is clean at that line. Lookups are untouched, so a
missing method or type still fails however the reader's values are typed. The new
overload_with_reader_argument control fails without the change.

A stale plan selection was hiding a bug. reference__timeseries's exclude = [13] was
written for a listing of IngestResult's signatures, which is now block 16 — so it had been
skipping the latest-datapoint example and compiling the prose instead. With the selection
corrected, that example turns out to name a DatapointDTO that does not exist: latest
answers DataWrapper<DataCollection<DatapointString>>.

docs/reference/labels.md, policies.md and tenant.md had runnable Java and no plan.
Each is a reference page of separate examples, one of which deletes what an earlier one
creates, so each gets independent = true — which also clears their two "variable already
defined" compile errors.

Also in this branch

  • Rust reads the same way. get_items() lends the vector, so taking the first result in one
    expression meant get_items_mut().remove(0) — a mutable borrow and a destructive take, in order
    to read. The 24 sites now bind the response and index it.
  • units.by_external_ids → by_external_id on three pages, its prose and the coverage table:
    the sync Python client was renamed (dataplatform-rust-sdk 96fe3ea) and no longer differs from the
    async and Rust clients.
  • timeseries().list(100, 43L) resolves the data set id through datasets().byIds rather than
    naming a bare number.
  • The tailings page keeps its if let Some(..). It had grown an unwrap_or("") and an
    is_empty() test purely to satisfy the compile tier; the tier now drops E0282 on a page that
    leaves a function to the reader, which is information rustc cannot have. A control fails without
    that change.

Verified

Run the way CI runs it, all zero-fail: the structure tier under system Python with no SDK
(440 passed), test_compile.py and test_api_surface.py against SDK main and the
platform's default branch, and test_harness.py. Whole suite: 741 passed, 78 skipped — the
skips are the live tutorials, which need a stack.

Left alone deliberately

  • value_type = "numeric" on ~6 pages. The type error is fixed; whether the server accepts
    "numeric" rather than the float32/BIGINT used elsewhere is a question for the live tier.
  • timeseries().list(100, 43L) — the Java client has no external-id overload, so the example
    passes a numeric id with a comment saying so. Whether the page should show resolving the id
    first is a separate call.
  • The tutorials job now reaches its gate for the first time in this history and skips:
    no DOCTEST_BASE_URL is configured, so there is no stack to run against. Nothing here has
    been proven against a live backend, and the two items above are the candidates for red when
    one is configured. That is a different verdict from this one.

🤖 Generated with Claude Code

JosteinGj and others added 10 commits September 28, 2026 13:31
…th setters

`DataWrapper.getItems()` returns `Collection`, which has no `get(int)`, so the
27 `.getItems().get(0)` reads never compiled; the site's existing idiom is
`.getItems().iterator().next()`.

`Timeseries` has no static `of(...)` factory, and `NodeModel`'s setters are
Lombok-generated and return `void`, so neither the factory nor the fluent chain
that followed it existed. Build the series with plain setter statements.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
`ResourceForm` never existed — resources are created from `Resource` (a
`NodeModel`), which is what `resources().create` takes. `ResourceSearch` was
replaced by `SearchBody<F>`; its own javadoc names it as one of the four classes
it folded together. `resources().create` answers with
`GraphDataWrapper<NodeModel, EdgeProxy>`.

`ResourceNetwork.nodes()` is a `Set<NodeModel>`, so the 16 traversals that
mapped `Resource::getExternalId` over it named a receiver the stream never
holds.

`timeseries().list(int, long)` takes the data set's numeric id; there is no
overload for an external id. `DataSetFilter` has no `externalIdPrefix`, so the
retail page now looks its data set up with `datasets().byIds`, which is what its
Python and Rust tabs beside it already did.

The tutorial's step path used a `Metric` record that only appeared in the
complete program at the end of the page, so a reader following the steps could
not compile step 2. It is introduced where the steps first need it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
javac resolves neither an overload nor a target type through an argument it
could not resolve, so an unresolved reader-supplied name is reported twice: once
as the name, and again as a type error on the enclosing call — a different line,
which is why the existing per-line suppression never caught it. Five pages were
failing on calls that compile: `timeseries().ingest(Map.of(id, readings))` with
`readings` left to the reader, and the tutorial's `host`.

The stubbed pass already gives every such name a type, so it can now retract as
well as add: a *typing* error that disappears once the reader's names are typed
was theirs, not the page's. Lookups are untouched — a missing method or type
still fails however the reader's values are typed — and `invalid method
reference` stays hard, being a claim about the SDK's shape rather than about what
flows into a call.

The new `overload_with_reader_argument` control fails without this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
…id is a method

`Event::new(external_id, r#type, event_time)` takes three arguments, and the
struct's `r#type` and `event_time` are plain values rather than `Option`s — the
field's own doc comment says why: the server rejects an event with no type, so
there is no useful default. 27 pages passed the external id alone and then
assigned `Some(...)` to both. `docs/reference/events.md` already had it right and
is the shape they now follow.

`Node::external_id(&self) -> &str` is a method, not a field, and the `&str` it
returns needs no `.as_str()`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
Rust pages carry their own imports — nothing is supplied for them — and the
tutorial's step blocks showed only the ones for building a client, so a reader
following the steps had no `IdAndExtId`, `DataWrapper`, `TimeSeries`, `HashMap`,
`System` or `Utc`. The complete programs at the end of the page already listed
them; the steps now name what each one uses, by the same paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
…r mutably

`FileUpload::new_with_destination_path` answers `io::Result<Self>` — it reads the
file's metadata and refuses a directory — so the four pages that build one need
`?` before they can set a field on it.

`DataWrapper::get_items` lends the vector (`&Vec<T>`); taking an item out of it
needs `get_items_mut`. All 24 `get_items().remove(0)` reads were wrong, though
only 10 were visible: rustc stops before the borrow checker on any unit that
still has an unresolved name, so the rest were hidden behind other errors on
their own pages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
…types

`Datapoint.value` is `Option<f64>` and `Datapoint.timestamp` a `DateTime<Utc>`,
so five pages parsed strings that were never strings. `TimeSeries.value_type` is
`Option<String>`, which `"numeric".into()` cannot reach on its own.

`BasicEventFilter` is gone: the criteria are `EventFilter` and the request body
that carries `limit`, `sort` and `cursor` is `EventFilterForm`.
`docs/reference/events.md` already described it that way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
The compile tier cannot infer through a function the page leaves to the reader:
every constraint on `inflow` is another such call, so rustc has nowhere to infer
from and asks for an annotation. Naming the type is what these pages owed the
reader anyway — it is the only statement of what their `latest`, `sum_over`,
`tolerance` and `trigger_level` are expected to return.

An annotation does not survive being destructured — rustc does not carry it
through `if let Some(x)` when the scrutinee came from a name it could not
resolve — so the tailings page takes the level as a `&str` and tests it for
emptiness instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
The Python client has no `BasicEventFilter`, and `filter()` is keyword-only:
either `filter=` with an `EventFilter` or the individual criteria, never both,
and paging is always an argument of the call rather than a field of the filter.
Seven pages passed a positional `EventFilter` wrapping a `BasicEventFilter`,
which is the Rust shape, not this one. `docs/reference/events.md` already
described the Python call correctly.

This clears the api-surface job: 222 checks, no failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
…atch the pages

`docs/reference/labels.md`, `policies.md` and `tenant.md` had runnable Java and no
plan, so coverage refused them. Each is a reference page whose blocks are separate
examples — one of them deleting what an earlier one creates — so each gets
`independent = true`, which is also what stops the two "variable already defined"
compile errors.

Seven plans' block counts had fallen behind their pages. `reference__timeseries`
shows why the pin exists: its `exclude = [13]` was written for the signature
listing, which is now block 16, so for some time it skipped the latest-datapoint
example and compiled the prose. With the selection corrected, the example turns
out to name a `DatapointDTO` that does not exist — `latest` answers
`DataWrapper<DataCollection<DatapointString>>`.

`reference__resources` also picks up the blocks #101 and #102 added for the
`/functions` endpoints. They are appended, so the plan's `inject before = 8` still
lands on "Traverse the graph", which is the block it is there for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
JosteinGj and others added 4 commits September 29, 2026 14:52
…ng its type

rustc reports "type annotations needed" at the *use* of a value destructured out
of a call it could not resolve, and no annotation the page could write reaches
it: `if let Some(level) = trigger_level(..)` cannot carry one, and the value's
only other constraint is another function the page leaves to the reader. There is
no Rust counterpart of the Java stubbed pass to settle it either — a stub would
need a type of its own, from the same missing information.

So E0282 is dropped on any unit that leaves a name to the reader. It can never be
how a renamed or removed SDK symbol shows up; that is E0425, E0433, E0412 or
E0599, none of which this touches. What it does cost is naming: a page whose own
code is genuinely ambiguous stops being caught, if it also leaves a name to the
reader.

That trade is worth it because the alternative was contorting the prose. The
tailings page had grown `unwrap_or("")` and an `is_empty()` test in place of a
plain `if let Some(..)`, purely to satisfy this check. It reads as a reader would
write it again. The `reader_supplied_destructured` control fails without the
change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
…on clients

The sync client's `by_external_ids` was renamed to `by_external_id`
(dataplatform-rust-sdk 96fe3ea), so it no longer differs from the async client,
the blocking client or Rust. The page said the two spellings differed; the
coverage table said so too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
`DataWrapper.getItems()` answers a `List` (datahub-platform#141), so the first of
one result is `getItems().get(0)` — which is what these pages said before they
were rewritten to `getItems().iterator().next()` to compile against the
`Collection` it used to be. All 35 reads go back, including the eight that were
written that way from the start, so the site reads one way. `reference/client.md`
now says the results are an ordered list, since that is what makes the index
meaningful.

`timeseries().list(int, long)` takes a numeric data set id and there is no
external-id overload, so the example resolves the id through `datasets().byIds`
rather than naming a bare `43L`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
`DataWrapper::get_items` lends the vector, so reading the first of one result in
a single expression meant `get_items_mut().remove(0)` — a mutable borrow and a
destructive take, in order to read. Binding the response first and indexing it
says what the code does, and matches the Python and Java tabs beside it, which
both index.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
@JosteinGj
JosteinGj merged commit 478f596 into master Sep 29, 2026
4 checks passed
@JosteinGj
JosteinGj deleted the fix/docs-match-sdk-api branch September 29, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant