Skip to content
This repository has been archived by the owner on Apr 13, 2019. It is now read-only.

Commit

Permalink
op update projections: changed + semantics to can replace; misc f…
Browse files Browse the repository at this point in the history
…ixes
  • Loading branch information
Konstantin Sobolev committed Oct 15, 2017
1 parent bf1c984 commit 7c8eb66
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public class UpdateOperationRouterTest {
" @String \"path.3\"",
" path /.:`record`/bestFriend",
" inputType UserRecord",
" inputProjection (id, firstName )",
" inputProjection +(id, firstName )",
" outputProjection :`record` (id, firstName)",
" }",
"}"
Expand Down Expand Up @@ -222,10 +222,10 @@ public void testPath2WithUpdate() throws PsiProcessingException {
@Test
public void testPath3WithUpdate() throws PsiProcessingException {
testRouting(
"/users/1:record/bestFriend(id)>:record(id)",
"path.3", // should not select path.2 because required field is missing from update projection
"/users/1:record/bestFriend+(id)>:record(id)",
"path.3", // should not select path.2 because we're asking to replace best friend and only path3 supports it
"/ '1' :record / bestFriend",
"( +id )",
"( id )",
1,
":record ( id )"
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import ws.epigraph.invocation.filters.CreateRequestValidationFilter;
import ws.epigraph.invocation.filters.CustomRequestValidationFilter;
import ws.epigraph.invocation.filters.ReadResponsePruningFilter;
import ws.epigraph.invocation.filters.UpdateRequestValidationFilter;
import ws.epigraph.schema.operations.*;
import ws.epigraph.service.operations.*;

Expand Down Expand Up @@ -99,7 +98,7 @@ public OperationFilterChains(
//update
new DefaultOperationInvocationFiltersChain<>(
Arrays.asList(
new UpdateRequestValidationFilter<>(),
//new UpdateRequestValidationFilter<>(),
new ReadResponsePruningFilter<>()
)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
*
* @author <a href="mailto:konstantin.sobolev@gmail.com">Konstantin Sobolev</a>
*/
@Deprecated
public class UpdateRequestValidationFilter<Rsp extends OperationResponse>
extends AbstractOperationInvocationFilter<UpdateOperationRequest, Rsp, UpdateOperationDeclaration> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ void raiseNoTagsError(
}

// should be schema-wide instance ?
return new OpInputProjectionsPsiParser(context).parseModelProjection(
return new OpInputProjectionsPsiParser(OpInputProjectionsPsiParser.FLAG_REQUIRED, context).parseModelProjection(
keyType,
true,
inputModelProjectionPsi,
Expand Down Expand Up @@ -207,14 +207,15 @@ void raiseNoTagsError(
paramPsi,
context
);
// should be schema-wide instance ?
else paramModelProjection = new OpInputProjectionsPsiParser(context).parseModelProjection(
paramType,
paramPsi.getPlus() != null,
paramModelProjectionPsi,
resolver,
context
);
// should be schema-wide instance ?
else paramModelProjection =
new OpInputProjectionsPsiParser(OpInputProjectionsPsiParser.FLAG_REQUIRED, context).parseModelProjection(
paramType,
paramPsi.getPlus() != null,
paramModelProjectionPsi,
resolver,
context
);

return new OpParam(paramName, paramModelProjection, EpigraphPsiUtil.getLocation(paramPsi));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@
* @author <a href="mailto:konstantin.sobolev@gmail.com">Konstantin Sobolev</a>
*/
public final class OpInputProjectionsPsiParser extends PostProcessingOpProjectionPsiParser {
public static final String FLAG_REQUIRED = "required";
public static final String FLAG_CAN_REPLACE = "'can replace'";
public static final String FLAGGED = "flagged";

public OpInputProjectionsPsiParser(MessagesContext context) {
public OpInputProjectionsPsiParser(String flagSemantics, MessagesContext context) {
super(
null,
new OpFlagSynchronizer("required", context)
new OpFlagSynchronizer(flagSemantics, context)
);
}

public OpInputProjectionsPsiParser(MessagesContext context) {
this(FLAGGED, context);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.Map;

/**
* Synchronizes 'flag' between model and entity projections for datum types and entity types with retro tags.
*
* @author <a href="mailto:konstantin.sobolev@gmail.com">Konstantin Sobolev</a>
*/
public class OpFlagSynchronizer extends OpProjectionTransformer {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2017 Sumo Logic
*
* 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 ws.epigraph.url.projections.req.update;

import org.jetbrains.annotations.NotNull;
import ws.epigraph.lang.MessagesContext;
import ws.epigraph.lang.TextLocation;
import ws.epigraph.projections.op.OpEntityProjection;
import ws.epigraph.projections.op.OpTagProjectionEntry;
import ws.epigraph.projections.req.ReqEntityProjection;
import ws.epigraph.projections.req.ReqTagProjectionEntry;
import ws.epigraph.types.TypeKind;
import ws.epigraph.url.projections.req.AbstractReqTraversal;

import java.util.Map;
import java.util.Optional;

/**
* Checks that if req projection is marked for 'replace' then corresponding
* op projection is marked as 'can replace'
*
* @author <a href="mailto:konstantin.sobolev@gmail.com">Konstantin Sobolev</a>
*/
public class ReqCanReplaceChecker extends AbstractReqTraversal {
public ReqCanReplaceChecker(final @NotNull MessagesContext context) {
super(context);
}

@Override
protected boolean visitVarProjection(
final @NotNull ReqEntityProjection projection,
final @NotNull OpEntityProjection guide) {

if (projection.flag() && !guide.flag() && guide.type().kind() != TypeKind.PRIMITIVE) // can always replace primitive
{
String description = Optional.ofNullable(currentEntityDataDescription).orElse(
String.format("data for type '%s'", projection.type().name())
);

TextLocation location = Optional.ofNullable(currentEntityDataLocation).orElse(projection.location());

context.addError("Operation doesn't support replacing data for " + description, location);
}

if (projection.type().kind() == TypeKind.ENTITY) {
for (final Map.Entry<String, ReqTagProjectionEntry> entry : projection.tagProjections().entrySet()) {
String tagName = entry.getKey();
ReqTagProjectionEntry rtpe = entry.getValue();
OpTagProjectionEntry gtpe = guide.tagProjection(tagName);

if (gtpe == null)
context.addError(String.format("Malformed projection: unsupported tag '%s'", tagName), rtpe.location());
else if (rtpe.projection().flag() && !gtpe.projection().flag())
context.addError(
String.format("Operation doesn't support replacing data for tag '%s'", tagName),
rtpe.location()
);
}
}

// todo replace on tails? (and model tails too)

return super.visitVarProjection(projection, guide);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import ws.epigraph.lang.MessagesContext;
import ws.epigraph.url.projections.req.DefaultReqProjectionConstructor;
import ws.epigraph.url.projections.req.PostProcessingReqProjectionPsiParser;
import ws.epigraph.url.projections.req.postprocess.ReqRequiredChecker;

/**
* @author <a href="mailto:konstantin.sobolev@gmail.com">Konstantin Sobolev</a>
Expand All @@ -28,7 +27,7 @@ public final class ReqUpdateProjectionPsiParser extends PostProcessingReqProject

public ReqUpdateProjectionPsiParser(boolean includeAllFromOp, MessagesContext context) {
super(
new ReqRequiredChecker(context),
new ReqCanReplaceChecker(context),
new ReqUpdatePostProcessor(context),
DefaultReqProjectionConstructor.updateProjectionDefaultConstructor(includeAllFromOp)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ private ReqTestUtil() {}
return parseOpEntityProjection(OpInputProjectionsPsiParser::new, varDataType, projectionString, resolver);
}

public static @NotNull OpEntityProjection parseOpUpdateEntityProjection(
@NotNull DataType varDataType,
@NotNull String projectionString,
@NotNull TypesResolver resolver) {

// same parser for now
return parseOpEntityProjection(OpInputProjectionsPsiParser::new, varDataType, projectionString, resolver);
}

public static @NotNull OpEntityProjection parseOpDeleteEntityProjection(
@NotNull DataType varDataType,
@NotNull String projectionString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ public class ReqUpdateProjectionsParserTest {
epigraph.String.type
);

private final OpEntityProjection personOpProjection = parsePersonOpInputEntityProjection(
private final OpEntityProjection personOpProjection = parsePersonOpUpdateEntityProjection(
lines(
":(",
" id,",
" `record` (",
" id {",
" ;param1 : epigraph.String,",
" },",
" bestFriend :(+id, `record` (",
" +id,",
" +bestFriend :(id, `record` (",
" id,",
" bestFriend :`record` (",
" id,",
" firstName",
Expand Down Expand Up @@ -150,8 +150,8 @@ public void testParseRecord() {
@Test
public void testParseMap() {
testParse(
":record ( friendsMap [ '1';param = 'foo', '2'!ann = true ]+( :id ) )",
":record ( friendsMap [ '1';param = 'foo', '2'!ann = true ]+( :id ) )"
":record ( friendsMap [ '1';param = 'foo', '2'!ann = true ]( :id ) )",
":record ( friendsMap [ '1';param = 'foo', '2'!ann = true ]( :+id ) )"
);
}

Expand All @@ -163,6 +163,11 @@ public void testParseList() {
);
}

@Test
public void testReplace() {
testParse(":record ( +bestFriend :id )");
}

@Test
public void testParseRecursiveWrongOp() {
//noinspection ErrorNotRethrown
Expand Down Expand Up @@ -193,14 +198,17 @@ public void testParseRecursiveDifferentOpRecursion() {

@Test
public void testUpdateField() {
testParse(":record ( +bestFriend :id )");
testParse(
":record ( bestFriend :id )",
":record ( bestFriend :+id )"
);
}

@Test
public void testUpdateModel() {
testParse(
":+record ( id )",
":+record ( id )"
":record ( id )",
":record ( +id )"
);
}

Expand Down Expand Up @@ -231,13 +239,13 @@ public void testParseModelTail() {
// negative cases

@Test
public void testRequiredTag() {
testParseFail(":record(bestFriend:record(id))");
public void testFieldReplaceNotSupported() {
testParseFail(":record ( +worstEnemy ( id ) )");
}

@Test
public void testRequiredField() {
testParseFail(":record(bestFriend:(id,record()))");
public void testTagReplaceNotSupported() {
testParseFail(":+record ( id )");
}

@Test
Expand Down Expand Up @@ -304,7 +312,7 @@ private void testParse(String expr, String expectedProjection) {
assertEquals(expectedProjection, actual);
}

private @NotNull OpEntityProjection parsePersonOpInputEntityProjection(@NotNull String projectionString) {
return ReqTestUtil.parseOpInputEntityProjection(dataType, projectionString, resolver);
private @NotNull OpEntityProjection parsePersonOpUpdateEntityProjection(@NotNull String projectionString) {
return ReqTestUtil.parseOpUpdateEntityProjection(dataType, projectionString, resolver);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package ws.epigraph.projections.gen;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ws.epigraph.lang.TextLocation;
import ws.epigraph.types.TagApi;

import java.util.*;
Expand Down Expand Up @@ -51,6 +53,9 @@ public abstract class GenGuidedProjectionTraversal<
private final Set<VP> visitedEntities = Collections.newSetFromMap(new IdentityHashMap<>());
private final Set<MP> visitedModels = Collections.newSetFromMap(new IdentityHashMap<>());

protected @Nullable String currentEntityDataDescription; // e.g. a field name or a map value reference
protected @Nullable TextLocation currentEntityDataLocation;

public boolean traverse(@NotNull VP projection, @NotNull GVP guide) {
if (visitedEntities.contains(projection)) return true;
else {
Expand Down Expand Up @@ -168,6 +173,9 @@ protected boolean traverse(@NotNull RMP projection, @NotNull GRMP guide) {
}

protected boolean traverse(@NotNull RMP projection, @NotNull GRMP gp, @NotNull FPE fpe, @NotNull GFPE gfpe) {
currentEntityDataDescription = String.format("field '%s'", fpe.field().name());
currentEntityDataLocation = fpe.location();

return visitFieldProjectionEntry(projection, gp, fpe, gfpe) &&
traverse(fpe.fieldProjection(), gfpe.fieldProjection());
}
Expand All @@ -181,15 +189,23 @@ public boolean traverse(@NotNull FP fp, @NotNull GFP gfp) {
}

protected boolean traverse(@NotNull MMP projection, @NotNull GMMP guide) {
//noinspection unchecked
return visitMapModelProjection(projection, guide) &&
traverse(projection.itemsProjection(), guide.itemsProjection());
if (!visitMapModelProjection(projection, guide))
return false;

currentEntityDataDescription += " value";
currentEntityDataLocation = projection.itemsProjection().location();

return traverse(projection.itemsProjection(), guide.itemsProjection());
}

protected boolean traverse(@NotNull LMP projection, @NotNull GLMP guide) {
//noinspection unchecked
return visitListModelProjection(projection, guide) &&
traverse(projection.itemsProjection(), guide.itemsProjection());
if (!visitListModelProjection(projection, guide))
return false;

currentEntityDataDescription += " item";
currentEntityDataLocation = projection.itemsProjection().location();

return traverse(projection.itemsProjection(), guide.itemsProjection());
}

protected boolean traverse(@NotNull PMP projection, @NotNull GPMP guide) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class UpdateRequestUrlPsiParserTest extends NonReadRequestUrlPsiParserTes
"resource users : map[String,Person] {",
" update {",
" inputType UserRecord",
" inputProjection (id, firstName)",
" inputProjection +(id, firstName)",
" outputProjection [required]( :`record` (id, firstName) )",
" }",
"}"
Expand Down
Loading

0 comments on commit 7c8eb66

Please sign in to comment.