Configuration Linting¶
Rift includes a powerful configuration linter (rift-lint) that validates imposter configuration files before loading them. This helps catch common issues early and ensures your configurations will work correctly.
It reads the same formats --configfile does: JSON (.json) and YAML (.yaml, .yml). A YAML config must be a sequence of imposters at the document root, which is the only shape --configfile loads — the single-imposter and {"imposters": [...]} forms are JSON-only, and using one in YAML is reported as E046. As in the engine, the format is decided by the content, not the extension: a .yaml file whose text starts with { or [ is read as JSON and may use any JSON shape.
Installation¶
# Via Homebrew (macOS/Linux)
brew tap achird-labs/rift
brew install rift
# rift-lint is included
# Via crates.io
cargo install rift-lint
# Via Docker (for CI/CD)
docker pull zainalpour/rift-lint:latest
Quick Start¶
# Lint a directory of imposters
rift-lint ./imposters/
# Lint with strict mode (warnings become errors)
rift-lint ./imposters/ --strict
# Using Docker
docker run --rm -v $(pwd):/imposters zainalpour/rift-lint .
Why Use the Linter?¶
The linter catches issues that would otherwise cause problems at runtime:
- Port conflicts: Multiple imposters trying to use the same port
- Invalid headers: Header values that aren't strings (arrays, numbers, booleans)
- Malformed predicates: Invalid JSONPath selectors, bad regex patterns
- JavaScript mistakes: Syntax errors in function
waitscripts (including the{"inject": …}spelling), JavaScriptdecoratescripts,injectresponses,injectpredicates andproxy.predicateGenerators[].inject, and JavaScript_rift.scriptbodies, parsed with the engine's own JavaScript engine (see E028 and E040). A Rhaidecorateis not parsed as JavaScript - Missing fields: Required configuration that's absent
- Engine refusals: Shapes the engine rejects at load, such as a repeated header name in a single-valued header object, a non-object
_behaviors, or an EJS tag the config loader does not evaluate
CLI Options¶
rift-lint [OPTIONS] <PATH>
Arguments:
<PATH> Path to an imposter file (.json, .yaml, .yml) or a directory of them
(a directory is scanned one level deep, not recursively)
Options:
-f, --fix Auto-fix issues where possible
-o, --output Output format: text (default), json
-e, --errors-only Only show errors (hide warnings)
-s, --strict Treat warnings as errors
--no-parse Lint files verbatim, without rendering EJS tags (alias: --noParse)
-h, --help Print help
-V, --version Print version
With -o json, stdout carries only the JSON result (an empty result when no files are found); the
banner, progress and --fix messages go to stderr.
The imposter JSON Schema¶
The grammar rift-lint checks is also published as a JSON Schema (draft 2020-12), generated from
the engine's own config types so it cannot drift from what the engine reads. It ships in every
release's sdk-conformance-<version>.tar.gz as schema/imposter.schema.json and is checked in at
sdk-conformance/schema/imposter.schema.json.
A build with the schema feature regenerates it:
cargo run -p rift-lint --features schema -- schema --out sdk-conformance/schema/imposter.schema.json
The released rift-lint binary is built without that feature — it would carry the whole engine —
so use the checked-in or packaged file. The linter's own key lists (predicate operators and
parameters, behavior names, fault kinds, proxy modes, …) are tested against the schema, and the
predicate names come from the same crate the engine reads them from.
Templated files¶
A JSON or YAML file with EJS <% %> tags is rendered before it is linted, exactly as --configfile
renders it: the same four tags, the same refusals, include and stringify paths relative to the
file, and process.env read from the environment rift-lint runs in. So the documented
"port": <%= process.env.PORT || '4545' %> lints clean, and an included file's imposters are
checked too.
- A tag the engine would refuse, or an included file it cannot read, is E049, with the engine's own message. The rest of that file is not checked.
- A
process.envtag with no default whose variable is unset, or anyprocess.envtag whose variable is set to a value that is not valid Unicode, is W013. Run the lint with the same environment rift will have, or give the tag a default. --no-parselints the text as it is, matchingrift --no-parse. Use it for a--datadirand for JSON sent toPOST /imposters, which the engine never preprocesses, so a literal<%there is data. The TUI validates imports this way.- A line and column in
E001orW012count in the rendered document, and the finding says so, because anincludeor a substitution moves them. --fixnever rewrites a templated file, because it would write the rendered values over the tags.
Validation Rules¶
Errors¶
Errors indicate issues that will prevent the imposter from loading correctly.
| Code | Description | Example |
|---|---|---|
| E001 | File could not be read, or is not valid JSON or YAML (a multi-document YAML stream included) | Missing comma, unquoted string, unreadable path |
| E002 | Port conflict — more than one imposter declares the same port, inside one file ({"imposters": [...]} or [...]) or across files. An absent or null port is auto-assigned and never conflicts; 0 is reported as E005 instead |
Two imposters on port 4545 |
| E003 | A required imposter field — port, protocol or stubs — is missing or null |
No port or stubs field |
| E004 | Invalid protocol | Protocol is "ftp" instead of "http" |
| E005 | Port out of range, or 0 — the engine auto-assigns 0 like an absent port, but a config file must pin its ports |
Port 70000 (max is 65535), port 0 |
| E010 | Unbalanced brackets in JSONPath | $.user[0 missing ] |
| E013 | Invalid regex | [invalid( |
| E018 | A header array (is.headers, defaultResponse.headers) contains a null, object or array element; the engine refuses the document. Strings, numbers and booleans are legal, and the engine coerces them, as Mountebank does |
"Accept": ["text/html", null] |
| E006 | Stub responses must be a non-empty array — the engine refuses a stub without a response, as Mountebank does. Formerly W002 for an empty list |
{"predicates": [...]} or "responses": [] |
| E007 | Predicate is not an object | "predicates": ["equals"] |
| E008 | Predicate has no operator | {"caseSensitive": true} on its own |
| E009 | Unknown predicate operator | {"equalz": {"path": "/a"}} |
| E011 | JSONPath missing selector field |
"jsonpath": {} |
| E014 | Response has no response type — the engine refuses it with unrecognized response type, as Mountebank does. A null, or an empty inject/fault string, counts as absent |
Neither is, proxy, inject, fault, _rift nor a flat statusCode/headers/body: {}, {"fault": ""}, only behaviors |
| E015 | Invalid HTTP status code | "statusCode": 999 |
| E016 | statusCode is neither an integer within 0-65535 nor a string. The engine refuses the file |
"statusCode": true |
| E017 | Empty header name | "headers": {"": "value"} |
| E021 | Headers is not an object | "headers": [] |
| E022 | Proxy to URL does not start with http:// or https:// |
"to": "ftp://host/x" |
| E023 | Proxy to is not a string URL |
"to": 8080 |
| E024 | Proxy missing required to field |
"proxy": {"mode": "proxyOnce"} |
| E025 | Invalid wait behavior value — a bare number must be a non-negative integer of milliseconds; anything else makes the engine refuse the file (before 0.18.0 it loaded and ignored the block's behaviors, all but repeat, with only a log line). null counts as absent. A behaviors array is checked as the engine runs it: every element is a step, so a wait is checked in every element a later null does not remove, and the finding names that element; "_behaviors": null falls back to behaviors. Also fires for a {min,max} range with min greater than max, and for an inverted stub-level delayRange entry (bounds may be numeric strings) — the engine refuses those outright. The inverted-range check is the one exception to that rule: the engine validates every behaviors element and both blocks regardless of which it will run, so the linter does too — an inverted range in an array element a later null removes, or in a _behaviors block ignored beside behaviors or a top-level repeat (#1709), is still reported |
"wait": [], "wait": 500.5, "wait": {"min": 100, "max": 10} |
| E026 | Unbalanced braces in JavaScript — the fallback when the script is not parsed: a build without the javascript feature, or a Rhai decorate. A parsed script reports E028 instead, so a { inside a string or comment is not miscounted |
function () { return 1; |
| E027 | Unbalanced parentheses in JavaScript — the fallback when the script is not parsed, as for E026 | function ( { return 1; } |
| E028 | JavaScript syntax error, found by parsing the script (never running it). Every release artifact includes this check; a build that opts out of the javascript feature falls back to brace and parenthesis counting (E026/E027) and says so with I004. Covers decorate and function wait scripts, and every inject: a response (when it is the response type the engine uses — not beside an is or proxy), a predicate at any depth, and a proxy.predicateGenerators entry. An inject is parsed as the engine wraps it (var __injectFn = <script>;), so an arrow, async or named function is accepted, as the engine accepts it |
A malformed decorate or inject function |
| E029 | Copy behavior item missing from |
{"into": "${token}"} |
| E030 | Copy behavior item missing into |
{"from": "body"} |
| E031 | Lookup behavior missing key |
Lookup with only fromDataSource |
| E032 | Lookup behavior missing fromDataSource |
Lookup with only key |
| E033 | Lookup behavior missing into |
Lookup with key and fromDataSource only |
| E034 | More than one operator in a single predicate | {"equals": {...}, "contains": {...}} — split them under and |
| E035 | repeat is not an integer from 1 to 4294967295, on the response or in an object-form block. 0 and null are refused by the engine, as Mountebank refuses them. A repeat in a behaviors array element beside a behavior is not checked: it is ignored (see W024) |
"repeat": 0 |
| E036 | script must specify exactly one of code, file or ref |
Both code and file given |
| E037 | Unknown script ref — no such entry in _rift.scripts |
"ref": "missing" |
| E038 | Script file (via ref) could not be read |
"file": "no-such.js" |
| E039 | A _rift.scripts entry uses ref itself (ref chains are not allowed) |
{"a": {"ref": "b"}} |
| E040 | JavaScript syntax error in a JavaScript _rift.script (inline, file or ref). Every release artifact includes this check; a build without the javascript feature has no fallback for it and reports I004 instead |
A malformed _rift.script body |
| E041 | Malformed _rift.fault.tcp: not a fault-type string or an object; an object form without a numeric probability or a string type; or a probability outside 0.0–1.0 |
"probability": 1.5, {"type": "RESET"} |
| E043 | Single-valued header object names one header twice, in different case (proxy.injectHeaders, _rift.fault.error.headers) |
{"X-Id": "a", "x-id": "b"} |
| E044 | Single-valued header object names one header twice, byte-identically (proxy.injectHeaders, _rift.fault.error.headers). is.headers is excluded: a repeat there is merged into two header lines on purpose |
{"X-Id": "a", "X-Id": "b"} |
| E045 | Single-valued header object has a non-string value (proxy.injectHeaders, _rift.fault.error.headers) |
{"X-Id": 1} |
| E046 | A YAML document's root is not a sequence of imposters — the engine's YAML loader accepts only a top-level list, unlike --configfile's JSON, which also accepts a single imposter object or an {"imposters": [...]} wrapper |
port: 3000 at the document root |
| E047 | port is present but not a non-negative integer — the engine refuses the file at load (expected u16), and an integral float such as 3000.0 is no exception. null is reported as E003 instead, because the engine reads it as absent and auto-assigns a port |
"port": "3000", "port": 3000.5 |
| E048 | A response's behaviors block has a shape the engine does not read: a _behaviors that is not an object, or a behaviors that is neither an object nor an array, is refused at load; a non-object, non-null element of a behaviors array is skipped |
"_behaviors": [null, null, null, null, "cmd"], "behaviors": "wait", "behaviors": [5] |
| E049 | The engine would refuse to preprocess the file: an EJS tag it does not evaluate, or an include/stringify file that cannot be read. The message is the engine's own |
"body": "<% for (x) %>", <% include 'missing.json' %> |
| E050 | A config file's intercept block sets returnCaKey: true. The engine refuses the file, because a config file has no response to return the generated CA key in |
"intercept": {"returnCaKey": true} |
| E051 | A behavior value the engine refuses the file for, on any response type: a copy or lookup item that is not an object; a missing or malformed using (an object with method regex, jsonpath or xpath, a string selector, boolean regex options, and an ns only on xpath, as an object of prefix → URI strings), or a regex selector that does not compile (the engine also refuses a JSONPath or XPath selector that does not compile, which rift-lint does not check); a from that is neither a field name nor an object of names, or a copy.from or lookup key.from object without exactly one key (copy behavior "from" field must have exactly one key, as Mountebank says), judged as written, so even in an array element a later null clears; a non-string into; a fromDataSource without a csv object of string path and keyColumn and a string delimiter; a decorate that is not a string; a shellTransform that is neither a command string nor an array of them |
"copy": {"from": "path", "into": "${P}"} |
| E052 | An unknown proxy mode — Rift refuses the imposter; use proxyOnce, proxyAlways or proxyTransparent, or omit mode for proxyOnce |
"mode": "proxyEverything" |
| E053 | A proxy predicateGenerators entry's predicateOperator is not one of equals, deepEquals, contains, startsWith, endsWith, matches, exists. Rift refuses the imposter; every recording would otherwise match every request |
"predicateOperator": "bogus" |
| E054 | A header value (is.headers, defaultResponse.headers) is null or an object; the engine refuses the document. A number or boolean is fine: the engine coerces it, as Mountebank does |
"X-Request-Id": null |
| E055 | A proxy predicateGenerators entry the recorder cannot read: not an object, neither matches nor inject, a non-string inject, a matches that is not an object, a matches key that is not a request field (method, path, query, headers, body, requestFrom, ip, form), a non-boolean caseSensitive or a non-string except. Rift refuses the imposter |
"predicateGenerators": [{"matches": {"pathh": true}}] |
| E056 | Response has more than one of is, proxy, inject, fault and _rift.script — the engine refuses it with each response object must have only one response type, as Mountebank does for its four. A null, or an empty inject/fault string, counts as absent. _rift.script beside the flat statusCode/headers/body form counts as two as well (#1729). Formerly W003, a warning for is + proxy only. To pair a status with a transport fault, put _rift.fault inside is |
{"is": {...}, "fault": "CONNECTION_RESET_BY_PEER"} |
| E057 | Proxy to has a path, or is not a URL. The engine refuses it with proxy.to must not contain a path '<path>', as Mountebank does; the path is read as a WHATWG URL reads it, so a trailing /, a query or a fragment is fine. Use pathRewrite with "from": "" to prefix the upstream path |
"to": "http://host:8080/api" |
| E058 | An imposter with "mode": "binary" has a matches predicate or a jsonpath/xpath selector, at any depth under not/and/or. The engine refuses it with Mountebank's message (the matches predicate is not allowed in binary mode, the jsonpath predicate parameter is not allowed in binary mode). Only the exact string binary counts |
"mode": "binary", ... {"matches": {"body": "^AQ"}} |
| E059 | A behaviors array element with more than one behavior key (wait, lookup, copy, shellTransform, decorate; a null one counts). The engine refuses it with Each behavior object must have only one behavior type, as Mountebank does. repeat and any other key do not count, and the _behaviors object form is never refused for it. Formerly W018, a warning |
"behaviors": [{"decorate": "…", "copy": {…}}] |
| E060 | A behaviors array element with no behavior key but some other key, such as a misspelt behavior or a {"repeat": n} element. The engine refuses it with Unrecognized behavior: "<key>", as Mountebank does. Put repeat on the response. A custom key beside one behavior is allowed, as is any key in the _behaviors object form |
"behaviors": [{"wait": 100}, {"repeat": 2}] |
| E061 | A behaviors array element whose copy, lookup or shellTransform is a list, an empty one included, even when a later null removes it. The engine refuses it with Mountebank's first error (copy behavior "from" field required, lookup behavior "key" field required, shellTransform behavior "shellTransform" field must be a string, …). Write one element per item; the _behaviors object form takes a list |
"behaviors": [{"shellTransform": ["./a.sh", "./b.sh"]}] |
Warnings¶
Warnings indicate potential issues that may cause unexpected behavior.
| Code | Description | Example |
|---|---|---|
| W001 | Privileged port | Port 80 requires root access |
| W004 | Invalid JSON body | Body isn't JSON but Content-Type is application/json. Not reported when _rift.templated or a copy/lookup/decorate/shellTransform behavior rewrites the body before it is served |
| W006 | Content-Length header is a numeric string below 10 |
"Content-Length": "5" |
| W007 | A proxy mode in a non-canonical spelling — Rift reads mode case-insensitively, so it works, but the canonical names are proxyOnce, proxyAlways, proxyTransparent |
"mode": "proxyalways" |
| W008 | shellTransform contains a potentially dangerous command |
"shellTransform": "rm -rf /tmp/x" |
| W009 | Non-function behavior | "wait": "return 100" without function wrapper |
| W010 | Protocol tcp is not yet implemented and will fail at runtime |
"protocol": "tcp" |
| W011 | Unknown TCP fault type — the fault will not fire at runtime | {"type": "NONSENSE"} |
| W012 | Number literal cannot be kept as written — the engine reads it as the nearest double (a .yaml/.yml file is not checked, even one holding JSON text) |
"body": {"big": 123456789012345678901234567890} is served as 1.2345678901234568e29 |
| W013 | A <%= process.env.VAR %> tag cannot substitute its variable where rift-lint runs: the variable is unset and the tag has no default, so it renders empty, or it is set to a value that is not valid Unicode, so the tag renders its default or empty. The engine logs the same warning at load. The document is linted as rendered |
"port": <%= process.env.PORT %> with PORT unset |
| W014 | A response's _rift.script uses ctx.state (or flow_store), or its _rift.stateOps is a non-empty array, but the imposter has no _rift.flowState. State is then auto-provisioned in memory — not persisted, not shared across a cluster. Formerly E042 (renumbered in #1156: it was always a warning, and the letter now matches) |
ctx.state.get(...) without flowState |
| W015 | A _mode: "binary" body — on an is response or on defaultResponse — that is not valid base64, or is not a string at all (a non-string body is serialized to JSON text first, so it can never decode). The engine serves it anyway, as the raw text with x-rift-binary-error: true, or as a 500 under strictBehaviors. Checked with the engine's own decoder, not an approximation; like the engine, it ignores line breaks and any other character outside the base64 alphabet |
"body": "not!valid!base64!", "_mode": "binary" |
| W016 | _rift.scriptEngine.defaultEngine is not rhai, javascript or js. A script that names no engine, and whose file extension does not decide it, fails to build with it. The rest of the imposter loads, so a stale value with no such script is only a warning |
"defaultEngine": "lua" |
| W017 | A key the engine parses and does not act on: _rift.metrics, _rift.proxy, recordMatches: true, _rift on a proxy, inject or fault response, or a _behaviors/behaviors block setting anything besides repeat on a fault or _rift-only response. The engine reports the same keys as config_key_ignored in _rift.warnings |
"recordMatches": true |
| W019 | _rift.conditional that can never answer 304: on a script response (_rift.script), or on an is response (or a bare _rift response without a script, served as is: {}) in a stub whose top-level equals/deepEquals method predicate is a single string other than GET/HEAD. A method under or/not/and, or matched with matches/exists, is not judged. _rift on a proxy, inject or fault response is W017's. The engine reports the same shapes as conditional_never_runs in _rift.warnings |
{"equals": {"method": "POST"}} + "conditional": true |
| W020 | A proxy predicateGenerators entry with a key the engine does not read — it reads inject, matches, caseSensitive, predicateOperator, except, jsonpath, xpath and ignore. The imposter loads and the key is ignored when recording; the engine reports the same keys as config_key_ignored in _rift.warnings |
{"matchs": {"path": true}} |
| W021 | A proxy predicateGenerators jsonpath or xpath that is not an object with a string selector (and, for xpath, an ns object of strings), or an ignore that is not an object mapping a field to a key, an array of keys or a nested object. The engine records the whole body (or ignores the filter) instead |
"jsonpath": "$.id" |
| W022 | A top-level fault whose name the engine does not know. Names are case-sensitive, as in Mountebank. The imposter loads, and the fault is ignored at serve time, as in Mountebank: the stub answers an empty response completed by defaultResponse. The engine logs the same warning at load and reports it as config_key_ignored in _rift.warnings. An empty name is E014's |
{"fault": "NON_EXISTENT_FAULT"} |
| W023 | A statusCode string, in is or defaultResponse, that is not a status code and that no copy, lookup or decorate on the response rewrites. The engine keeps it, as Mountebank does, and reads it when it writes the response, so every request answers 500 ERR_HTTP_INVALID_STATUS_CODE. A defaultResponse status is never rewritten. A string Node reads as 100-999 (" 404 ", "0x194") and the empty string are not flagged |
"statusCode": "${code}" with no copy |
| W024 | A repeat beside a behavior in a behaviors array element. The engine ignores it, as Mountebank does, and admits any value there: only the response's repeat (or the _behaviors object form's) repeats it. Releases up to 0.23.0 read it as the response's repeat |
"behaviors": [{"wait": 100, "repeat": 3}] |
| W025 | _behaviors beside behaviors or a top-level repeat. The engine ignores it whole, its repeat included, as Mountebank does (#1709): move its behaviors into the behaviors array and its repeat onto the response. An empty {} or null _behaviors is not reported |
{"repeat": 2, "_behaviors": {"wait": 100}} |
Info¶
Informational messages about configuration patterns.
| Code | Description |
|---|---|
| I001 | JSONPath uses a jsonpath-plus shorthand (.[, or a slice end or step of 0) that Rift reads as Mountebank does but standard JSONPath reads differently |
| I002 | Proxy targets localhost |
| I003 | Response uses the Rift _rift extension (not Mountebank-compatible) |
| I004 | This build omits the javascript feature, so the run's JavaScript was not syntax-checked (no E028/E040). Reported once per run, not per script. Every release artifact has the feature; only a source build that opts out with --no-default-features can report this |
| I005 | _rift.dataset or _rift.sequencing: a carrier field that round-trips through the admin API for an embedder's extension. The standalone engine does not read it |
Retired codes. A code number is never reused. E012 was never assigned. E042 was renumbered
to W014 in #1156 — it had always been reported as a warning, and a consumer filtering on the E
prefix saw a different rule set from one filtering on severity. A rule's letter now always matches
its severity. E019/E020 were retired in #1358: the engine coerces number and boolean header
values, as Mountebank does, so they are not findings. W005 became E054, because the engine
refuses a null header value. W002 (an empty responses) was folded into E006 in #1372, when the engine
began refusing an empty responses as it does an absent one. W003 (is beside proxy) became
E056 in #1551, when the engine began refusing any response with two types, for every pair.
W018 (a behaviors element with several behaviors) became E059 in #1587, when the engine began
refusing it, as Mountebank does.
Auto-Fix¶
The --fix flag automatically corrects a header array the engine would refuse in is.headers
(the E018 finding). It runs only when the lint found at least one error, and repairs every imposter in
each of the three shapes --configfile loads: a single imposter object, the {"imposters": [...]}
wrapper (what rift save writes), and a bare [...] array. The file keeps its shape:
--fix rewrites JSON only. A .yaml/.yml file is reported and never rewritten:
re-serializing it would put JSON text under a YAML name, which the engine would then silently
read back as JSON.
- An array containing a
null, object or array element → that element quoted in place, as its JSON text (strings, numbers and booleans are legal and left as written)
A scalar null or object header value (E054) is not fixed: there is no value to infer.
A templated file is never rewritten (see Templated files): the rewrite
would replace its <% %> tags with what they rendered to.
A file that gives the same key twice anywhere is never rewritten. --fix re-serializes the
whole document from its parsed form, and a repeated key does not survive parsing — the first value
is already gone. Rather than write that loss to disk, --fix skips the file and names the key it
would have dropped.
How to resolve it depends on where the repeat is:
- In
is.headers, a repeated name is how a stub sends the same header twice, and the engine merges it on purpose — so the fix is not to delete one of them. Write the values as an array instead, which means the same thing and survives a rewrite:"Set-Cookie": ["a=1", "b=2"]. - Anywhere else —
proxy.injectHeaders,_rift.fault.error.headers, or an ordinary field such asport— a repeat is a mistake and only one of the values was ever going to be used. Keep the one you meant. In the two single-valued header objects this is also reported as E044; an array is not a valid alternative there.
A file with a number --fix cannot write back digit-for-digit is never rewritten either. A
number wider than a 64-bit integer, or with more significant digits than a double can distinguish, is
held as the nearest double once parsed — so rewriting the file would change it:
| Written in the file | What a rewrite would write |
|---|---|
123456789012345678901234567890 |
1.2345678901234568e+29 |
0.1000000000000000055511151231257827 |
0.1 |
0.30000000000000001 |
0.3 |
An ordinary float is not affected: any number a double holds in its shortest form — 7e23,
1.23e-30, 0.10018513143495411 — is written back with its digits intact (a positive exponent
gains an explicit +: 7e23 is written as 7e+23).
--fix skips the file and names each such number with its line and column; every lint run, --fix
or not, also reports each one as W012. The engine reads the
file the same way, so it already serves the right-hand value for that number — the rewrite
would only have made the file agree with it, silently. Resolve it by writing the value you mean: the
rounded number if that is what you want served, or, if a response body must carry the exact digits,
give the body as a string, which the engine sends verbatim:
"body": "{\"big\": 123456789012345678901234567890}". Set Content-Type: application/json in
headers yourself when you do — the engine adds it only for a body written as a JSON object or
array.
A number that only changes spelling — 0.10 → 0.1, 1e2 → 100.0 — is formatting, not loss,
and does not stop the rewrite.
One more thing --fix does that is easy to miss: it rewrites the entire file, so object keys come
back in sorted order and the original formatting is not preserved. Formatting is all a rewrite
changes beyond the headers it reports: a file it would change in any other way — a repeated key, or
one of the numbers above — is skipped instead.
CI/CD Integration¶
GitHub Actions¶
Or with a direct command:
- name: Lint Imposters
run: |
docker run --rm -v ${{ github.workspace }}:/imposters \
zainalpour/rift-lint:latest . --strict
GitLab CI¶
lint:
image: zainalpour/rift-lint:latest
script:
- rift-lint ./imposters/ --output json > lint-results.json
artifacts:
reports:
codequality: lint-results.json
Pre-commit Hook¶
#!/bin/bash
# .git/hooks/pre-commit
if [ -d "imposters" ]; then
rift-lint ./imposters/ --strict
if [ $? -ne 0 ]; then
echo "Imposter linting failed. Please fix errors before committing."
exit 1
fi
fi
Common Issues and Fixes¶
Header Values Must Be Strings¶
Problem:
Fix:
JavaScript Must Be Function Expression¶
Problem:
Fix:
Exit Codes¶
| Code | Meaning |
|---|---|
| 0 | No errors (warnings allowed unless --strict) |
| 1 | Errors found (or warnings in --strict mode) |
Library Usage¶
The linter is also available as a Rust library for integration into other tools (like rift-tui):
Library entry points differ from the CLI in what they can see:
- E002 (port conflicts) is reported by every entry point (issue #1156).
lint_file,lint_json,lint_yaml,lint_valueandlint_documentreport a port repeated inside the one document they lint;lint_directoryreports a port shared across its files, always against the first in byte-wise sorted path order (case-sensitive, soB.jsonsorts beforea.json). To lint several documents as one run yourself, record each in aPortUsesand reportPortUses::conflicts()once — and lint them withlint_document_in_run, which reports noE002of its own, so a within-file conflict is not reported twice. - E044, E046 and W012 need the raw text, so
lint_value(which starts from an already-parsed value) cannot report them.lint_file,lint_json,lint_yaml, andparse_document/parse_yaml_document+lint_documentcan. lint_file,lint_jsonandlint_yamlrender EJS tags first, like the CLI, unlessLintOptions { no_parse: true }is passed.
use rift_lint::{lint_file, lint_json, lint_value, LintOptions, LintResult};
use std::path::Path;
// Lint a file from disk
let result = lint_file(Path::new("imposter.json"), &LintOptions::default());
// Lint a JSON string (useful for in-memory validation)
let json = r#"{"port": 4545, "protocol": "http", "stubs": []}"#;
let result = lint_json(json, "inline", &LintOptions::default());
// Lint already-parsed JSON
let value: serde_json::Value = serde_json::from_str(json).unwrap();
let result = lint_value(&value, "inline", &LintOptions::default());
// Check results
if result.has_errors() {
for issue in &result.issues {
println!("[{}] {}: {}", issue.severity.label(), issue.code, issue.message);
}
}
Add to your Cargo.toml:
[dependencies]
# Library only (no CLI), keeping JavaScript syntax checking (E028/E040)
rift-lint = { path = "../rift-lint", default-features = false, features = ["javascript"] }
Without features = ["javascript"], JavaScript is not syntax-checked and the run reports
I004.
See Also¶
- rift-verify - Test imposters by making requests
- Mountebank Compatibility - Configuration format reference