Skip to content

Commit

Permalink
Add timestamp.valid evaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Bos authored and simonbos committed Oct 20, 2023
1 parent cc19223 commit 653b00b
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ private void buildValue(
processEnumConstraints(fieldDescriptor, fieldConstraints, valueEvaluator);
processMapConstraints(fieldDescriptor, fieldConstraints, valueEvaluator);
processRepeatedConstraints(fieldDescriptor, fieldConstraints, forItems, valueEvaluator);
processTimestampConstraints(fieldDescriptor, fieldConstraints, valueEvaluator);
}

private void processFieldExpressions(
Expand Down Expand Up @@ -360,6 +361,21 @@ private void processRepeatedConstraints(
valueEvaluatorEval.append(listEval);
}

private void processTimestampConstraints(
FieldDescriptor fieldDescriptor,
FieldConstraints fieldConstraints,
ValueEvaluator valueEvaluatorEval) {
if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE
|| !fieldDescriptor.getMessageType().getFullName().equals("google.protobuf.Timestamp")) {
return;
}
FieldDescriptor secondsDesc = fieldDescriptor.getMessageType().findFieldByName("seconds");
FieldDescriptor nanosDesc = fieldDescriptor.getMessageType().findFieldByName("nanos");
TimestampEvaluator timestampEvaluatorEval =
new TimestampEvaluator(secondsDesc, nanosDesc, fieldConstraints.getTimestamp().getValid());
valueEvaluatorEval.append(timestampEvaluatorEval);
}

private static List<CompiledProgram> compileConstraints(List<Constraint> constraints, Env env)
throws CompilationException {
List<Expression> expressions = Expression.fromConstraints(constraints);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package build.buf.protovalidate.internal.evaluator;

import build.buf.protovalidate.ValidationResult;
import build.buf.protovalidate.exceptions.ExecutionException;
import build.buf.validate.Violation;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import java.util.ArrayList;
import java.util.List;

/**
* A specialized evaluator for applying some {@link build.buf.validate.TimestampRules} (only the
* `valid` rule currently) to an {@link com.google.protobuf.Timestamp} message. This is handled
* outside CEL which handles {@link com.google.protobuf.Timestamp} as an abstract type, thus not
* allowing access to the message fields.
*/
class TimestampEvaluator implements Evaluator {
private final long maxTimestamp = +253402300799L;
private final long minTimestamp = -62135596800L;

private final Descriptors.FieldDescriptor secondsDescriptor;
private final Descriptors.FieldDescriptor nanosDescriptor;
private final boolean valid;

/** Constructs a new evaluator for {@link build.buf.validate.TimestampRules} messages. */
TimestampEvaluator(
Descriptors.FieldDescriptor secondsDescriptor,
Descriptors.FieldDescriptor nanosDescriptor,
boolean valid) {
this.secondsDescriptor = secondsDescriptor;
this.nanosDescriptor = nanosDescriptor;
this.valid = valid;
}

@Override
public ValidationResult evaluate(Value val, boolean failFast) throws ExecutionException {
Message timestampValue = val.messageValue();
if (timestampValue == null) {
return ValidationResult.EMPTY;
}
List<Violation> violationList = new ArrayList<>();
if (valid) {
long seconds = (long) timestampValue.getField(secondsDescriptor);
int nanos = (int) timestampValue.getField(nanosDescriptor);

String errorMessage = "";
if (seconds < minTimestamp) {
errorMessage = "timestamp before 0001-01-01";
} else if (seconds > maxTimestamp) {
errorMessage = "timestamp after 9999-12-31";
} else if (nanos < 0 || nanos >= 1e9) {
errorMessage = "timestamp has out-of-range nanos";
}

if (errorMessage.length() != 0) {
Violation violation =
Violation.newBuilder()
.setConstraintId("timestamp.valid")
.setMessage(errorMessage)
.build();
violationList.add(violation);
if (failFast) {
return new ValidationResult(violationList);
}
}
}
return new ValidationResult(violationList);
}

@Override
public boolean tautology() {
return !valid;
}
}
83 changes: 83 additions & 0 deletions src/main/java/build/buf/validate/TimestampRules.java

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/main/java/build/buf/validate/TimestampRulesOrBuilder.java

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 18 additions & 17 deletions src/main/java/build/buf/validate/ValidateProto.java

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/main/resources/buf/validate/validate.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3697,4 +3697,9 @@ message TimestampRules {
id: "timestamp.within",
expression: "this < now-rules.within || this > now+rules.within ? 'value must be within %s of now'.format([rules.within]) : ''"
}];

// `valid` specifies that this field, of the `google.protobuf.Timestamp` type, must adhere to the documented specification:
// * the `seconds` field must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.
// * the `nanos` field must be from 0 to 999,999,999 inclusive.
bool valid = 10;
}

0 comments on commit 653b00b

Please sign in to comment.