IV.2
Lint definitions before saving
You need this: When non-developers edit forms in Creator.
Run survey-core's linter on every definition Creator saves: unknown properties, unknown variables, missing names, conflicting names. Refuse the save with the report, so a broken form never reaches your users. Warnings alone don't block the save: they are saved and shown. Here the SurveyJS service lints, next to this app.
The SurveyJS service is called at http://surveyjs:3000. If it isn't running, the endpoint answers
503 "SurveyJS service is not running": start it with docker compose up -d surveyjs.
Try this
Code that just ran
These regions are read from the files that served this page, the same lines the Server Integration page shows.
shared/client/lint-definition.js · sjs:IV.2.client
View on GitHub
// Creator: surface the server's lint report instead of a silent failure
async function saveDefinition(definition) {
const res = await fetch(`/api/forms/${formId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(definition)
});
const report = res.status === 204 ? {} : await res.json(); // 422 { errors, warnings } · 200 { warnings } · { error }
if (report.errors) creator.notify(report.errors.map(e => e.message).join("\n"), "error");
else if (report.warnings) creator.notify(report.warnings.map(e => e.message).join("\n"), "info");
else if (report.error) creator.notify(report.error, "error");
return res.ok;
}
creator.saveSurveyFunc = async (saveNo, callback) => callback(saveNo, await saveDefinition(creator.JSON));
routes/examples/lint-definition.php · sjs:IV.2.server
View on GitHub
// PUT /api/forms/{id} — lint the definition from Creator before storing it
Route::put('/api/forms/{id}', function (Request $request, string $id) {
Gate::authorize('edit-forms');
$definition = json_decode($request->getContent(), flags: JSON_THROW_ON_ERROR); // the raw body keeps {} as {} (see III.1)
try {
$lint = Http::baseUrl(config('surveyjs.service_url'))->timeout(10)->post('/schema', $definition);
} catch (ConnectionException $e) { // cURL error 28 is a timeout
return str_contains($e->getMessage(), 'cURL error 28')
? response()->json(['error' => 'SurveyJS service timed out'], 504)
: response()->json(['error' => 'SurveyJS service is not running at '.config('surveyjs.service_url')], 503);
}
if ($lint->status() === 422 && $lint->json('errors') !== null) { // errors: refuse the save, report them as they are
return response($lint->body(), 422)->header('Content-Type', 'application/json');
}
$warningsOnly = $lint->status() === 200 && array_keys((array) $lint->json()) === ['warnings'];
if (! $warningsOnly && ($lint->status() !== 200 || $lint->body() !== '{}')) { // fail closed: nothing else counts as clean
Log::error('SurveyJS service: unexpected answer from /schema', ['status' => $lint->status(), 'body' => $lint->body()]);
abort(502, 'SurveyJS service error');
}
DB::table('forms')->updateOrInsert(['key' => $id], ['json' => json_encode($definition,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION | JSON_THROW_ON_ERROR)]);
return $warningsOnly ? response()->json(['warnings' => $lint->object()->warnings]) : response()->noContent();
});
Definition: shared/definitions/creator-load-save.json