SurveyJS Server Integration · PHP / Laravel IV.2 · Lint definitions before saving
You work in a private copy of the demo database, kept until it has been unused for 24 hours.

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.

This step on the Server Integration page Source on GitHub

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

  1. Save a definition with (visibleIf: "{nosuch} = 1") or : Creator's notification shows the 422 errors, and nothing is saved.
  2. Save one with : it is saved, and the notification shows the 200 warning.
  3. A , or Creator's own Save, returns 204.

Code that just ran

These regions are read from the files that served this page, the same lines the Server Integration page shows.

Client · 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));
Server · 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