
Supported schemas, file safety, and troubleshooting
Source:vignettes/troubleshooting.Rmd
troubleshooting.RmdSupported input and transport
specmill reads local JSON, YAML, and YML documents in OpenAPI 3.0/3.1 or Swagger 2.0 format. It supports a subset of each standard; parsing is not full OpenAPI validation. These guides follow the current development version, so check your toolkit pin if an older release rejects a documented feature.
Generation additionally requires each schema to pass local validation
against its official OpenAPI JSON Schema. References outside the
document lack coverage and block generation by default. Inspect
plan$validation for schema errors or unsupported coverage.
See validation
policy.
| Feature | Current behavior |
|---|---|
| Schema references | Same-document and local-file references are supported; remote references are not downloaded |
| Scalar parameters | Path, query, header, and supported cookie parameters; paths must be required |
| Array/object parameters | Flat primitive arrays and flat objects with supported styles; nested shapes are diagnosed |
| JSON bodies | Scalars, nested objects/arrays, free-form values, supported composition, and bounded local recursion |
| Binary bodies | Raw vectors for application/octet-stream with a binary
string schema |
| Form bodies/uploads | Supported URL-encoded and multipart shapes, including Swagger
formData; unsupported encodings are diagnosed |
| Authentication | Generated API-key and HTTP bearer support; OAuth login/refresh and custom flows remain client-owned |
| HTTP method | Explicitly declared; no inferred GET/POST conversion |
| Pagination/batching | Explicit configuration or companion functions; a page
argument alone causes one request |
| Response interpretation | The helper decodes the actual content type; no generated response-schema validation |
See schema loading, parameter serialization, JSON bodies, and form uploads for the supported shapes and limits. Generated input checks cover supported constraints; custom helpers remain responsible for their serialization and service-specific behavior. Fixture selection also checks supported types, enums, bounds, lengths, and patterns. Do not interpret a chosen fixture as a valid live identifier or a safe operation to invoke. A GET endpoint may itself submit work.
read_operations() returns supported
operations, structurally valid but unsupported
unsupported_operations, an inventory, and
diagnostics. A complete public inputs plus
request mapping can handle supported metadata through a
client serializer. Generation labels that operation
client-mapped and retains the original reasons in
mapping_diagnostics. Malformed metadata or broken
references do not become safe merely by adding a mapping.
implementation: existing retains a declared manual
implementation and reports limitations in
retained_diagnostics. Unsupported selections stay visible
in coverage totals. They never authorize deleting old output.
Common errors
Read the operation’s disposition
Preview with
generate_client(root, config = 'specmill.yml', mode = 'plan').
The inventory keeps every discovered operation, including
exclusions. Its additive classification field explains the
disposition without changing the existing status values
used for selection and file ownership:
| Classification | Meaning and action |
|---|---|
ready |
Parsing found no blocker in the supported subset; verify the generated request before relying on live results |
policy_exclusion |
Project or service selection excluded the operation; review its
reason to reconsider it. Source documents still undergo
validation |
schema_defect |
A source-contract problem was detected; correct it. Mappings and retained implementations cannot bypass validator errors |
capability_gap |
specmill cannot handle the construct; consider a supported complete client mapping or leave it visible pending implementation |
review_required |
An unclassified error needs source/configuration review; no schema-validity claim is made |
Diagnostics also include code,
source_location (an escaped JSON pointer), and
guidance. For multiple transport limitations,
reason lists the observed messages and the structured
fields describe the first blocker. A later metadata error takes
precedence. Referenced input locations may point into shared schema
definitions. Mapped and retained operations preserve the original
classification as evidence in mapping_diagnostics or
retained_diagnostics; consult status for their
current generation disposition.
An unconstrained body is different from a missing body schema.
items: {} places no constraints on array elements. Swagger
2 body arrays inherit Draft 4 semantics that allow omitted
items; Swagger 2 non-body arrays and OpenAPI 3.0 array
schemas require it. These contexts receive different diagnostic codes.
See OpenAPI
3.0 Schema Objects, Swagger
2 parameters, and Draft
4 array validation.
Do not delete an operation merely because it has no named object properties: primitive arrays are meaningful inputs, and free-form bodies can represent a generator limitation. Local recursion, unresolved references, and external references now have separate codes. No diagnostic classification establishes full schema validity or live-service correctness.
Resolve common failures
| Symptom | Next step |
|---|---|
| Package/function not found | Install the reviewed toolkit, restart R, and inspect
packageVersion('specmill') and
find.package('specmill')
|
| Missing schema / pattern matched nothing | Resolve paths against root, check case, and use
sequences such as files: [schema.json]
|
| Unknown YAML field / invalid regex | Compare with Configuration; use case-sensitive stringr syntax |
| Unknown operation override | Match the original method/path, including path parameter spelling and trailing slash |
| Name collision | Supply distinct public names across all services, including case; avoid helper/callback names |
| Unresolved callback | Source your development callbacks into the explicitly supplied environment |
| Missing helper definition / helper argument | Define it in client R/ and map the actual helper
signature |
| Missing fixed contracts | Author independent fixtures for selected operations; use test
--dry-run to inspect gaps |
Invalid selected operation defaults |
Run mode = 'plan' and read the
parameter_default_schema or
parameter_default_transport diagnostics. Set a valid
default or default: null under the operation’s
parameters in the service YAML; see parameter
defaults
|
| Generated output is stale or protected | Inspect a fresh plan; apply reviewed input changes or resolve ownership |
| Formatter version mismatch | Install the exact Air version declared by the project; keep formatting policy consistent |
| Unsafe documentation tag | Put executable/custom roxygen handlers in client code, and use
docs fields for policy |
| Plan/input changed during generation | Stop concurrent edits/writers, make callbacks deterministic, then replan |
| HTTP failure | Diagnose authentication, input, request construction, schema drift, runtime behavior, and service status separately |
When upstream operation IDs collide, read_operations()
lists each conflicting name and every method/path using it. Its
policy = list(names = list(...)) example can be passed to
read_operations() after reviewing the names. For a
generated client, initialize_client() writes configuration
and returns collision diagnostics in
attr(result, 'configuration')$diagnostics. Edit the
affected service YAML names: map before generation. For
example:
names:
GET /qaAnnualPerformanceEvaluations/bySite: qa_annual_performance_evaluations_by_site
GET /qaAnnualPerformanceEvaluations/byPQAO: qa_annual_performance_evaluations_by_pqaoconfigure_client() preserves these reviewed names. Run
generate_client() with mode = 'apply', then
mode = 'check' to verify regeneration.
A normal return from check only establishes freshness
for reconciled output. Inspect diagnostics and run tests separately. For
reproducible generation, pin the toolkit and formatter alongside schema,
policy, and fixture versions.
Ownership and protected files
.specmill/manifest.json records output hashes and
generation input metadata. Keep it in Git. A header alone cannot grant
ownership. specmill protects unowned files, locally edited output, and
protected lifecycle states. It rejects groups containing undeclared
definitions or other top-level code.
Use reviewed adoption only after comparing a legacy file with the intended generated contract. Never compute adoption hashes across every file merely to silence protection. If your local change is intentional, move it into policy or a maintained callback, or retain that wrapper explicitly.
For a rename, the protected original blocks replacement so the old public function cannot silently remain beside a new one. Explicit exclusions can remove verified owned output; protected removals are reported as retained. Review the file actions rather than assuming all requested deletions happened.
Interrupted apply and recovery
Application parses R output and stages backups before mutation. It
holds one exclusive project lock, verifies writes, and restores backups
after an error. There is no cross-file filesystem transaction. A process
termination can leave a recovery journal and .specmill-lock
directory.
First stop other writers and confirm the interrupted R process has exited. Inspect the recovery plan without writing:
root <- normalizePath('/path/to/client', winslash = '/', mustWork = TRUE)
specmill::recover_client(root)When no live writer remains, remove only an empty stale lock, if present:
lock <- file.path(root, '.specmill-lock')
if (dir.exists(lock)) {
stopifnot(
!fs::is_link(lock),
length(list.files(lock, all.files = TRUE, no.. = TRUE)) == 0L
)
unlink(lock, recursive = TRUE)
}The normalized root and literal
.specmill-lock keep the target explicit. Do not remove a
nonempty directory or use a broad wildcard cleanup. Restore the reviewed
journal, then replan:
specmill::recover_client(root, 'apply')
specmill::generate_client(root, config = 'specmill.yml', mode = 'plan')Recovery recognizes specmill journals, validates destinations and backups, and retains the journal on failure. Never delete journals/backups to bypass recovery. The lock is not stolen automatically: directory age does not distinguish a dead process from a slow live writer.
Legacy R-list and compatibility interfaces
Existing R-list callers can continue to use the same generator:
spec <- list(
files = '/path/to/schema.json', helper = 'request_helper',
policy_version = 'reviewed-1'
)
specmill::generate_client('/path/to/client', spec, 'plan')Supply exactly one of spec and config. YAML
is the recommended entry point for new integrations because it keeps
service selection and policy explicit. render_operation()
and apply_files() are lower-level building blocks; most
projects should use generate_client() to retain
reconciliation and validation.
bind_tools(group, envir) installs extracted
compatibility functions into an explicit environment containing client
policy. It is intended for existing endpoint-table integrations. It does
not configure a new client automatically. The client supplies selection,
method allowlists, rendering, fixtures, and reporting policy. Legacy
diff counts and helper-call tests are regression evidence, not a general
compatibility proof.
The readiness group retains the versioned audit helpers
for clients already using that workflow. Bind into an environment with
asNamespace('specmill') as parent and set
audit_policy through its read_audit_policy()
reader. Credential names, issue metadata, badge paths, and test-tier
descriptions belong to the client’s audit policy. Prefer the client’s
existing thin audit command.
Reporting a reproducible problem
Include the toolkit version, operating system, generation mode, sanitized error, and the smallest local schema/policy that reproduces it. State which checks ran and whether any request was sent. Remove keys, tokens, private URLs, and private response content. Report at the specmill issue tracker.