Skip to contents

Supported 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_pqao

configure_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.