nstream.adapter.common.relay.DslInterpret Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nstream-adapter-common Show documentation
Show all versions of nstream-adapter-common Show documentation
General server-side data flow templates with Swim
// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Redis Source Available License 2.0 (RSALv2) Agreement;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://redis.com/legal/rsalv2-agreement/
//
// 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 nstream.adapter.common.relay;
import java.util.Optional;
import nstream.adapter.common.LabeledLog;
import swim.api.agent.AgentContext;
import swim.structure.Attr;
import swim.structure.Bool;
import swim.structure.Data;
import swim.structure.Expression;
import swim.structure.Item;
import swim.structure.Num;
import swim.structure.Record;
import swim.structure.Text;
import swim.structure.Value;
import swim.util.Log;
public final class DslInterpret {
private DslInterpret() {
}
public static void interpret(AgentContext context, Record schema, Item scope) {
interpret(context, LabeledLog.forAgentContext(context), schema, scope);
}
public static void interpret(AgentContext context, Log log, Record schema, Item scope) {
if (recordModelsOneDirective(schema)) {
interpretOne(createDirective(context, log, schema, scope));
} else {
for (Item item : schema) {
if (!interpretOne(createDirective(context, log, (Value) item, scope))) {
return;
}
}
}
}
private static boolean interpretOne(Directive> directive) {
try {
evaluateOrErr(directive);
} catch (InterpretException e) {
return false;
}
return true;
}
/**
* Utility method to evaluate a directive or log a helpful error if doing so
* results in failure.
*
* @param directive the {@code Directive} to evaluate
*
* @return {@code directive.evaluate()}
*
* @throws InterpretException if there is any issue in evaluating {@code
* directive}
*/
static Value evaluateOrErr(Directive> directive) throws InterpretException {
try {
return directive.evaluate();
} catch (InterpretException e) {
directive.log.error(e.getMessage());
directive.log.error("Could not interpret " + directive);
throw e;
}
}
static Directive> createDirective(AgentContext context, Log log, Value schema, Item scope) {
return createDirective(context, log, schema, scope, null);
}
static Directive> createDirective(AgentContext context, Log log, Value schema, Item scope, Directive> parent) {
final Item head = schema.head();
if (head instanceof Attr) {
if (head.toValue() instanceof Expression) {
return new Take<>(parent, context, log, scope, (Expression) head.toValue(), schema);
} else if (head.toValue().isDistinct()) {
throw new UnsupportedOperationException("Static Truncate directives not yet implemented");
} else {
switch (head.key().stringValue(null)) {
case "command":
return Command.fromSchema(parent, context, log, scope, schema);
case "foreach":
case "forEach":
return ForEach.fromSchema(parent, context, log, scope, schema);
case "if":
return If.fromSchema(parent, context, log, scope, schema);
case "substring":
return Substring.fromSchema(parent, context, log, scope, schema);
case "uripathencode":
case "uriPathEncode":
return UriPathEncode.from(parent, context, log, scope);
default:
log.error("schema " + schema + " does not model a known Directive");
throw new UnsupportedOperationException("Directive " + head.key().stringValue(null) + " not implemented");
}
}
} else {
log.error("schema " + schema + " does not model a Directive (no Attr head)");
throw new IllegalArgumentException("No Attr head present to indicate Directive type");
}
}
/**
* Utility method to evaluate a {@code Value} component into a {@code String}
* during DSL interpretation according to a rigid (but usually sufficient)
* ruleset.
*
* The ruleset is as follows:
*
* - If {@code component} has a String literal representation and is not a
* {@code Record}, then simply return the literal.
*
- If {@code component} is an {@code Expression}, then return the
* evaluation of the {@code Expression} against the calling {@code
* Directive}'s scope.
*
- If {@code component} is a {@code Record} whose {@code head()} is an
* {@code Attr}, then parse {@code component} into a {@code Directive} and
* return its evaluation.
*
- If {@code component} is a {@code Record} containing a series of {@code
* Values} (i.e. lacking any top-level {@code Fields}, then return the
* concatenation of recursively invoking {@code evaluateStringComponent()} on
* every such {@code Value}.
*
*
* @param source the current interpretation frame
* @param component the value within the frame that requires evaluation
*
* @return the fully-evaluated {@code component} relative to {@code source}
*/
static String evaluateStringComponent(Directive> source, Value component)
throws InterpretException {
final Optional basic = basicItemToString(source, component);
// Checked exceptions are a mistake -_-
return basic.isPresent() ? basic.get() : evaluateDynamicStringComponent(source, component);
}
private static String evaluateDynamicStringComponent(Directive> source, Value component)
throws InterpretException {
if (component instanceof Expression) {
return evaluateStringComponent(source, (Value) component.evaluate(source.scope));
} else if (component instanceof Record) {
if (recordModelsOneDirective((Record) component)) {
final Directive> toEvaluate;
try {
toEvaluate = createDirective(source.context, source.log, component, source.scope, source);
} catch (RuntimeException e) {
throw new InterpretException(e.getMessage());
}
final Value evaluated = evaluateOrErr(toEvaluate);
return basicItemToString(source, evaluated).orElseThrow(() -> {
source.log.error("Attempted to String-evaluate generated Record " + evaluated);
return new InterpretException("Generative DSL interpretation not yet implemented");
});
} else {
final StringBuilder sb = new StringBuilder(16 * component.length());
for (Item item : component) {
if (item instanceof Value) { // TODO: perhaps Fields are safe to include in this check
sb.append(evaluateStringComponent(source, (Value) item)); // recursive call
} else {
throw new InterpretException("Field evaluation currently not supported, encountered " + item);
}
}
return sb.toString();
}
} else {
source.log.error("component " + component + " has no stringify rules");
throw new InterpretException("component " + component + " has no stringify rules");
}
}
static Optional basicItemToString(Directive> source, Value component) {
if (component == null || !component.isDistinct()) {
return Optional.of("");
} else if (component instanceof Text || component instanceof Num || component instanceof Bool) {
return Optional.of(component.stringValue());
} else if (component instanceof Data) {
source.log.error("Attempted to directly stringify Data component " + component);
throw new IllegalArgumentException("Data-type component " + component + " must be wrapped by @dataEncode");
} else {
return Optional.empty();
}
}
static boolean recordModelsOneDirective(Record record) {
return record.head() instanceof Attr;
}
static Value evaluateHintedValue(Directive> directive, Value hint, Value literal, String tag)
throws InterpretException {
if (!hint.isDefined()) {
return literal.isDefined() ? literal : directive.scope.toValue();
} else if (hint instanceof Expression) {
try {
return hint.evaluate(directive.scope).toValue();
} catch (RuntimeException e) {
final String msg = "Failed to evaluate " + tag + " against " + directive.scope + " using " + directive;
throw new InterpretException(msg, e);
}
} else if (hint instanceof Record) {
return evaluateOrErr(createDirective(directive.context, directive.log, hint, directive.scope, directive));
} else {
return hint;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy