001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2023-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common.protobuf; 019 020import com.google.errorprone.annotations.CanIgnoreReturnValue; 021import com.google.errorprone.annotations.CheckReturnValue; 022import com.google.protobuf.Descriptors; 023import com.google.protobuf.MessageOrBuilder; 024 025import dev.enola.common.validation.Validation; 026import dev.enola.common.validation.Validations; 027 028import java.util.*; 029 030public class MessageValidators { 031 private final Map<Descriptors.Descriptor, List<MessageValidator<Object, MessageOrBuilder>>> 032 map = new HashMap<>(); 033 034 @CanIgnoreReturnValue 035 public MessageValidators register( 036 MessageValidator<?, ?> validator, Descriptors.Descriptor descriptor) { 037 map.computeIfAbsent(descriptor, descriptor1 -> new ArrayList<>()) 038 .add((MessageValidator<Object, MessageOrBuilder>) validator); 039 return this; 040 } 041 042 @CheckReturnValue 043 public Result validate(MessageOrBuilder message) { 044 var results = Result.newBuilder(); 045 validate(null, message, results); 046 return results.build(); 047 } 048 049 public void validate(MessageOrBuilder message, MessageValidators.Result.Builder r) { 050 validate(null, message, r); 051 } 052 053 public void validate( 054 Object context, MessageOrBuilder message, MessageValidators.Result.Builder results) { 055 var validators = map.get(message.getDescriptorForType()); 056 if (validators == null) { 057 return; 058 } 059 for (var validator : validators) { 060 validator.validate(context, message, results); 061 } 062 } 063 064 @CheckReturnValue 065 public Result validate(Object context, MessageOrBuilder message) { 066 var results = Result.newBuilder(); 067 validate(context, message, results); 068 return results.build(); 069 } 070 071 public static class Result { 072 private final Validations proto; 073 074 private Result(Validations proto) { 075 this.proto = proto; 076 } 077 078 public static Builder newBuilder() { 079 return new Builder(); 080 } 081 082 @CheckReturnValue 083 public Validations toMessage() { 084 return proto; 085 } 086 087 public void throwIt() throws ValidationException { 088 if (proto.getValidationsCount() > 0) { 089 throw new ValidationException(proto); 090 } 091 } 092 093 public static class Builder { 094 private final Validations.Builder result = Validations.newBuilder(); 095 096 private Builder() {} 097 098 public Result build() { 099 var proto = result.build(); 100 return new Result(proto); 101 } 102 103 public void add(Descriptors.GenericDescriptor descriptor, String error) { 104 // TODO Refine setPath() ... it should "aggregate" from "parents" ... 105 result.addValidations( 106 Validation.newBuilder() 107 .setPath(descriptor.getName()) 108 .setError(error) 109 .build()); 110 } 111 } 112 } 113}