Skip to content

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 wait scripts (including the {"inject": …} spelling), JavaScript decorate scripts, inject responses, inject predicates and proxy.predicateGenerators[].inject, and JavaScript _rift.script bodies, parsed with the engine's own JavaScript engine (see E028 and E040). A Rhai decorate is 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.env tag with no default whose variable is unset, or any process.env tag 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-parse lints the text as it is, matching rift --no-parse. Use it for a --datadir and for JSON sent to POST /imposters, which the engine never preprocesses, so a literal <% there is data. The TUI validates imports this way.
  • A line and column in E001 or W012 count in the rendered document, and the finding says so, because an include or a substitution moves them.
  • --fix never 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.

rift-lint ./imposters/ --fix

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 as port — 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

- name: Lint Imposters
  uses: docker://zainalpour/rift-lint:latest
  with:
    args: ./imposters/ --strict

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:

{
  "headers": {
    "Content-Length": 256,
    "X-Count": 10
  }
}

Fix:

{
  "headers": {
    "Content-Length": "256",
    "X-Count": "10"
  }
}

JavaScript Must Be Function Expression

Problem:

{
  "wait": "return Math.random() * 1000"
}

Fix:

{
  "wait": "function() { return Math.random() * 1000; }"
}


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_value and lint_document report a port repeated inside the one document they lint; lint_directory reports a port shared across its files, always against the first in byte-wise sorted path order (case-sensitive, so B.json sorts before a.json). To lint several documents as one run yourself, record each in a PortUses and report PortUses::conflicts() once — and lint them with lint_document_in_run, which reports no E002 of 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, and parse_document/parse_yaml_document + lint_document can.
  • lint_file, lint_json and lint_yaml render EJS tags first, like the CLI, unless LintOptions { 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