Class OptionChecks

java.lang.Object
io.github.flink.gcp.connector.base.options.OptionChecks

@Internal public final class OptionChecks extends Object
The checks every connector's option builders repeat, in one place.

All of them are about a Duration a user hands a builder, and all of them exist so the failure lands on the client rather than on a TaskManager, where a job has already started. Each clears the base module's multiple-consumer bar on its own; ADR-0068 carries the dated call-site survey behind that claim.

What they replace is not merely repetition. The ceiling constant stood in six files with its message written out in eight, so a new budget knob was correct only if its author remembered to copy four lines and word them the same way. Positivity had **three** message shapes for one check — "x must be positive", "x must be positive: <value>" and "x must be positive, but was <value>" — which is what a rule with no single implementation decays into. The value-carrying form won: a builder chain setting several durations otherwise leaves the user guessing which one the rejection meant. The millisecond floor was five private copies in two message shapes and two mechanics, and what a copy of it silently dropped was not the wording but the coverage: eleven setters converting a user value with toMillis() never had one.

Deliberately not a general-purpose precondition library: the next check needs an argument of its own, and "it is a precondition too" is not one.

  • Method Details

    • checkPositive

      public static Duration checkPositive(Duration duration, String name)
      Returns the duration, having checked it is present and positive.
      Parameters:
      duration - the duration to check
      name - the option name, for the failure message
      Returns:
      the duration
    • checkAtLeastOneMilli

      public static Duration checkAtLeastOneMilli(Duration duration, String name)
      Returns the duration, having checked it is present and at least one millisecond.

      Subsumes checkPositive(java.time.Duration, java.lang.String): zero and negative durations fail this check too, with this message rather than that one. Deliberately — a knob whose floor is a millisecond rejects Duration.ZERO for the same reason it rejects 500 µs, and a rejection saying only "must be positive" costs the user a second round trip when their next attempt is 500 µs.

      Which knobs need it is a property of what consumes the value, never of the knob's name: it belongs wherever a user's Duration is converted with Duration.toMillis() and a zero would be harmful — a RetrySchedule, a processing-time timer, a bounded await, a millisecond field of a Google API request, or gax's retry algorithm, which truncates its own delays the same way. Two knobs of one name, on a sink and on a source, can therefore have different floors (ADR-0068).

      A duration longer than about 292 million years throws ArithmeticException out of Duration.toMillis() instead of being rejected here. It still lands at the setter, which is where a failure belongs, so the conversion is left as it is and a test pins it.

      Parameters:
      duration - the duration to check
      name - the option name, for the failure message
      Returns:
      the duration
    • checkAtLeastOneMilliOrZero

      public static Duration checkAtLeastOneMilliOrZero(Duration duration, String name)
      Returns the duration, having checked it is present and either zero or at least one millisecond.

      For a knob this project **forwards to a vendor SDK that gives zero a meaning of its own** — gax reads a zero totalTimeout as "use the attempt count instead" and a zero RPC timeout as "let the call run indefinitely", and the BigQuery Storage writer reads a zero maxRetryDuration as "retry without a time limit". A setting this project merely passes through stays settable as the SDK defines it, so zero is forwarded rather than refused.

      The floor and the exemption are the same argument, not a compromise between two: the vendor reads these values with toMillis(), so a positive sub-millisecond value would arrive as zero and silently become the sentinel — which is how a retry ceiling set to give up almost at once turns into unlimited retry. Refusing it is what keeps zero meaning only what the user typed.

      Whether zero is legal is therefore a property of the SDK on the other side, never a loosening applied for convenience: a knob this project spends itself takes checkAtLeastOneMilli(java.time.Duration, java.lang.String), and a forwarded knob the vendor does not truncate takes neither — a floor promising millisecond granularity where none applies would be promising something untrue (the Pub/Sub subscriber's maxAckExtensionPeriod is that case).

      Parameters:
      duration - the duration to check
      name - the option name, for the failure message
      Returns:
      the duration
    • checkExpressibleInNanos

      public static Duration checkExpressibleInNanos(Duration duration, String name)
      Returns the budget, having checked it can be converted to nanoseconds.

      A longer one throws ArithmeticException from Duration.toNanos() instead — on a TaskManager, out of a teardown or a constructor, rather than here on the client, which is the whole point of the check (ADR-0068). Whether a given knob needs it is a property of what spends the budget, so the reason belongs at the call site.

      The message names the year count as well as the value, and that is load-bearing rather than decoration: Duration.toString() renders the ceiling as PT2562047H47M16.854775807S, which was measured to be exactly what a SQL user is shown, and no reader turns an hour count with a fractional second on it into "292 years". Tests pin the year count, so removing it fails rather than quietly making every rejection unreadable.

      Parameters:
      duration - the budget to check
      name - the option name, for the failure message
      Returns:
      the budget