001package com.streamconverter.validation;
002
003import java.time.Instant;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.List;
007import java.util.Objects;
008
009/**
010 * バリデーション結果を統一的に表現するクラス
011 *
012 * <p>JSON、XML、CSVなど、すべてのバリデーション処理の結果を一貫した形式で提供します。 バリデーションの成功/失敗、エラーメッセージ、実行時間などの情報を含みます。
013 *
014 * <p>使用例:
015 *
016 * <pre>
017 * ValidationResult result = ValidationResult.builder()
018 *     .validationType("JSON")
019 *     .schemaPath("schema/user.json")
020 *     .success(false)
021 *     .addError("Field 'email' is required")
022 *     .addError("Field 'age' must be a number")
023 *     .build();
024 * </pre>
025 */
026public final class ValidationResult {
027
028  private final String validationType;
029  private final String schemaPath;
030  private final boolean valid;
031  private final List<String> errors;
032  private final List<String> warnings;
033  private final Instant validationTime;
034  private final long execTimeMillis;
035  private final String dataSource;
036
037  private ValidationResult(
038      final String validationType,
039      final String schemaPath,
040      final boolean valid,
041      final List<String> errors,
042      final List<String> warnings,
043      final Instant validationTime,
044      final long execTimeMillis,
045      final String dataSource) {
046    this.validationType = validationType;
047    this.schemaPath = schemaPath;
048    this.valid = valid;
049    this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
050    this.warnings = Collections.unmodifiableList(new ArrayList<>(warnings));
051    this.validationTime = validationTime;
052    this.execTimeMillis = execTimeMillis;
053    this.dataSource = dataSource;
054  }
055
056  /**
057   * 成功した場合のバリデーション結果を作成
058   *
059   * @param validationType バリデーションタイプ("JSON", "XML", "CSV"など)
060   * @param schemaPath スキーマファイルのパス
061   * @param execTimeMillis 実行時間(ミリ秒)
062   * @return 成功を表すValidationResult
063   */
064  public static ValidationResult success(
065      final String validationType, final String schemaPath, final long execTimeMillis) {
066    return builder()
067        .validationType(validationType)
068        .schemaPath(schemaPath)
069        .success(true)
070        .executionTimeMillis(execTimeMillis)
071        .build();
072  }
073
074  /**
075   * 失敗した場合のバリデーション結果を作成
076   *
077   * @param validationType バリデーションタイプ("JSON", "XML", "CSV"など)
078   * @param schemaPath スキーマファイルのパス
079   * @param errors エラーメッセージのリスト
080   * @param execTimeMillis 実行時間(ミリ秒)
081   * @return 失敗を表すValidationResult
082   */
083  public static ValidationResult failure(
084      final String validationType,
085      final String schemaPath,
086      final List<String> errors,
087      final long execTimeMillis) {
088    final Builder builder =
089        builder()
090            .validationType(validationType)
091            .schemaPath(schemaPath)
092            .success(false)
093            .executionTimeMillis(execTimeMillis);
094
095    if (errors != null) {
096      for (final String error : errors) {
097        builder.addError(error);
098      }
099    }
100
101    return builder.build();
102  }
103
104  /**
105   * Builderインスタンスを作成
106   *
107   * @return 新しいBuilderインスタンス
108   */
109  public static Builder builder() {
110    return new Builder();
111  }
112
113  // Getters
114
115  /**
116   * バリデーションタイプを取得
117   *
118   * @return バリデーションタイプ
119   */
120  public String getValidationType() {
121    return validationType;
122  }
123
124  /**
125   * スキーマパスを取得
126   *
127   * @return スキーマファイルのパス
128   */
129  public String getSchemaPath() {
130    return schemaPath;
131  }
132
133  /**
134   * バリデーション結果が有効かどうかを取得
135   *
136   * @return 有効な場合true、無効な場合false
137   */
138  public boolean isValid() {
139    return valid;
140  }
141
142  /**
143   * エラーメッセージのリストを取得
144   *
145   * @return エラーメッセージのリスト(読み取り専用)
146   */
147  public List<String> getErrors() {
148    return errors;
149  }
150
151  /**
152   * 警告メッセージのリストを取得
153   *
154   * @return 警告メッセージのリスト(読み取り専用)
155   */
156  public List<String> getWarnings() {
157    return warnings;
158  }
159
160  /**
161   * バリデーション実行時刻を取得
162   *
163   * @return 実行時刻
164   */
165  public Instant getValidationTime() {
166    return validationTime;
167  }
168
169  /**
170   * 実行時間を取得
171   *
172   * @return 実行時間(ミリ秒)
173   */
174  public long getExecutionTimeMillis() {
175    return execTimeMillis;
176  }
177
178  /**
179   * データソース情報を取得
180   *
181   * @return データソース情報
182   */
183  public String getDataSource() {
184    return dataSource;
185  }
186
187  @Override
188  public String toString() {
189    return String.format(
190        "ValidationResult{type=%s, schema=%s, valid=%s, errors=%d, warnings=%d, executionTimeMs=%d}",
191        validationType, schemaPath, valid, errors.size(), warnings.size(), execTimeMillis);
192  }
193
194  @Override
195  public boolean equals(final Object other) {
196    boolean result = false;
197    if (this == other) {
198      result = true;
199    } else if (other instanceof ValidationResult) {
200      final ValidationResult that = (ValidationResult) other;
201      result =
202          valid == that.valid
203              && execTimeMillis == that.execTimeMillis
204              && Objects.equals(validationType, that.validationType)
205              && Objects.equals(schemaPath, that.schemaPath)
206              && Objects.equals(errors, that.errors)
207              && Objects.equals(warnings, that.warnings)
208              && Objects.equals(validationTime, that.validationTime)
209              && Objects.equals(dataSource, that.dataSource);
210    }
211    return result;
212  }
213
214  @Override
215  public int hashCode() {
216    return Objects.hash(
217        validationType,
218        schemaPath,
219        valid,
220        errors,
221        warnings,
222        validationTime,
223        execTimeMillis,
224        dataSource);
225  }
226
227  /** ValidationResult作成用のBuilderクラス */
228  public static class Builder {
229    private String typeVal;
230    private String schemaPathVal;
231    private boolean valid;
232    private final List<String> errors = new ArrayList<>();
233    private final List<String> warnings = new ArrayList<>();
234    private Instant timeVal = Instant.now();
235    private long execTimeMillis;
236    private String dataSourceVal;
237
238    /**
239     * バリデーションタイプを設定
240     *
241     * @param validationType バリデーションタイプ
242     * @return Builder
243     */
244    public Builder validationType(final String validationType) {
245      this.typeVal = validationType;
246      return this;
247    }
248
249    /**
250     * スキーマパスを設定
251     *
252     * @param schemaPath スキーマファイルのパス
253     * @return Builder
254     */
255    public Builder schemaPath(final String schemaPath) {
256      this.schemaPathVal = schemaPath;
257      return this;
258    }
259
260    /**
261     * バリデーション成功/失敗フラグを設定
262     *
263     * @param success 成功の場合true
264     * @return Builder
265     */
266    public Builder success(final boolean success) {
267      this.valid = success;
268      return this;
269    }
270
271    /**
272     * エラーメッセージを追加
273     *
274     * @param error エラーメッセージ
275     * @return Builder
276     */
277    public Builder addError(final String error) {
278      if (error != null && !error.isBlank()) {
279        this.errors.add(error.trim());
280      }
281      return this;
282    }
283
284    /**
285     * 警告メッセージを追加
286     *
287     * @param warning 警告メッセージ
288     * @return Builder
289     */
290    public Builder addWarning(final String warning) {
291      if (warning != null && !warning.isBlank()) {
292        this.warnings.add(warning.trim());
293      }
294      return this;
295    }
296
297    /**
298     * バリデーション実行時刻を設定
299     *
300     * @param validationTime 実行時刻
301     * @return Builder
302     */
303    public Builder validationTime(final Instant validationTime) {
304      this.timeVal = validationTime;
305      return this;
306    }
307
308    /**
309     * 実行時間を設定
310     *
311     * @param execTimeMillis 実行時間(ミリ秒)
312     * @return Builder
313     */
314    public Builder executionTimeMillis(final long execTimeMillis) {
315      this.execTimeMillis = execTimeMillis;
316      return this;
317    }
318
319    /**
320     * データソース情報を設定
321     *
322     * @param dataSource データソース情報
323     * @return Builder
324     */
325    public Builder dataSource(final String dataSource) {
326      this.dataSourceVal = dataSource;
327      return this;
328    }
329
330    /**
331     * ValidationResultインスタンスを構築
332     *
333     * @return ValidationResult
334     * @throws IllegalStateException 必須フィールドが設定されていない場合
335     */
336    public ValidationResult build() {
337      requireNonBlank(typeVal, "Validation type");
338      requireNonBlank(schemaPathVal, "Schema path");
339      ensureConsistency();
340      return new ValidationResult(
341          typeVal, schemaPathVal, valid, errors, warnings, timeVal, execTimeMillis, dataSourceVal);
342    }
343
344    private static void requireNonBlank(final String value, final String field) {
345      if (value == null || value.isBlank()) {
346        throw new IllegalArgumentException(field + " cannot be null or empty");
347      }
348    }
349
350    private void ensureConsistency() {
351      if (valid && !errors.isEmpty()) {
352        throw new IllegalStateException(
353            "ValidationResult cannot be marked as valid when errors are present");
354      }
355      if (!valid && errors.isEmpty()) {
356        addError("Validation failed (no specific error message)");
357      }
358    }
359  }
360}