diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/JsonProposalProvider.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/JsonProposalProvider.java index 1aff387c..eaafb01b 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/JsonProposalProvider.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/JsonProposalProvider.java @@ -195,10 +195,17 @@ protected Collection createObjectProposals(ObjectTypeDefinition type, } } + if (type.getAdditionalProperties() != null) { + String elementTitle = type.getLabel(); + String elementName = elementTitle != null? elementTitle : type.getAdditionalProperties().getLabel(); + if (elementName != null) { + elementName = String.format("(%s name)", elementName); + proposals.add(new Proposal(elementName + ":", elementName, null, null, elementName)); + } + } if (proposals.isEmpty()) { proposals.add(new Proposal("_key_" + ":", "_key_", null, null)); } - return proposals; } diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/Proposal.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/Proposal.java index f62ec46a..55a719d2 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/Proposal.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/Proposal.java @@ -30,6 +30,7 @@ public class Proposal { public final String displayString; public final String type; public final String description; + private final String selection; protected final Styler typeStyler = new StyledString.Styler() { @Override @@ -38,11 +39,16 @@ public void applyStyles(TextStyle textStyle) { } }; - public Proposal(String replacementString, String displayString, String description, String type) { + public Proposal(String replacementString, String displayString, String description, String type, String selection) { this.replacementString = replacementString; this.displayString = displayString; this.type = type; this.description = description; + this.selection = selection; + } + + public Proposal(String replacementString, String displayString, String description, String type) { + this(replacementString, displayString, description, type, ""); } /** @@ -93,9 +99,10 @@ public StyledCompletionProposal asStyledCompletionProposal(String prefix, int of StyledCompletionProposal proposal = null; if (Strings.emptyToNull(prefix) == null) { - proposal = new StyledCompletionProposal(replacementString, styledString, null, description, offset); + proposal = new StyledCompletionProposal(replacementString, styledString, null, description, offset, + selection); } else if (rString.toLowerCase().contains(prefix.toLowerCase())) { - proposal = new StyledCompletionProposal(rString, styledString, prefix, description, offset); + proposal = new StyledCompletionProposal(rString, styledString, prefix, description, offset, selection); } return proposal; diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/StyledCompletionProposal.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/StyledCompletionProposal.java index d2b788b9..a3c54333 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/StyledCompletionProposal.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/StyledCompletionProposal.java @@ -34,11 +34,13 @@ public class StyledCompletionProposal private final String description; /** Lower-cased prefix - content assist typeahead should be case-insensitive */ private final String prefix; + private final String selection; public StyledCompletionProposal(String replacement, StyledString label, String prefix, String description, - int offset) { + int offset, String selection) { this.label = label; this.replacementString = replacement; + this.selection = selection == null ? "" : selection; this.prefix = prefix != null ? prefix.toLowerCase() : null; this.replacementOffset = offset; this.description = description; @@ -82,10 +84,9 @@ public Point getSelection(IDocument document) { offset = replacementOffset - prefix.length(); } } - - int cursorPosition = offset + replacementString.length(); - - return new Point(cursorPosition, 0); + int replacementIndex = !"".equals(selection) ? replacementString.indexOf(selection) : -1; + int selectionStart = offset + (replacementIndex < 0 ? replacementString.length() : replacementIndex); + return new Point(selectionStart, selection.length()); } @Override diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/contexts/ComponentContextType.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/contexts/ComponentContextType.java index 4937c94c..656005bb 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/contexts/ComponentContextType.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/assist/contexts/ComponentContextType.java @@ -13,8 +13,8 @@ import java.util.Collection; import com.fasterxml.jackson.core.JsonPointer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.JsonNode; +import com.reprezen.swagedit.core.json.references.JsonReference; import com.reprezen.swagedit.core.model.AbstractNode; import com.reprezen.swagedit.core.model.Model; import com.reprezen.swagedit.core.schema.ComplexTypeDefinition; @@ -23,13 +23,13 @@ public class ComponentContextType extends ContextType { - private final ObjectNode componentRef; - + private final String componentRef; + public ComponentContextType(String value, String label, String componentSchemaPath) { super(value, label); - componentRef = new ObjectMapper().createObjectNode().put("$ref", "#/definitions/" + componentSchemaPath); + componentRef = "#/definitions/" + componentSchemaPath; } - + protected String getReferencePointerString() { return "/definitions/reference/properties/$ref"; } @@ -73,12 +73,17 @@ protected boolean isReferenceToComponent(Model model, JsonPointer pointer) { if (parentType instanceof ComplexTypeDefinition) { Collection types = ((ComplexTypeDefinition) parentType).getComplexTypes(); for (TypeDefinition type : types) { - if (componentRef.equals(type.getContent())) { + if (hasRefToComponent(type.getContent())) { return true; } } } - return componentRef.equals(parentType.getContent()); + return hasRefToComponent(parentType.getContent()); + } + + private boolean hasRefToComponent(JsonNode content) { + return content.hasNonNull(JsonReference.PROPERTY) + && componentRef.equals(content.get(JsonReference.PROPERTY).asText()); } } diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/preferences/KaiZenPreferencesUtils.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/preferences/KaiZenPreferencesUtils.java new file mode 100644 index 00000000..f8ef8238 --- /dev/null +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/preferences/KaiZenPreferencesUtils.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2016 ModelSolv, Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * ModelSolv, Inc. - initial API and implementation and/or initial documentation + *******************************************************************************/ +package com.reprezen.swagedit.core.preferences; + +import org.dadacoalition.yedit.preferences.PreferenceConstants; +import org.eclipse.jface.preference.IPreferenceStore; + +public class KaiZenPreferencesUtils { + public static int getTabWidth() { + IPreferenceStore prefs = org.dadacoalition.yedit.Activator.getDefault().getPreferenceStore(); + return prefs.getInt(PreferenceConstants.SPACES_PER_TAB); + } +} diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/quickfix/QuickFixer.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/quickfix/QuickFixer.java index 86a6feaa..3f1ea3e7 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/quickfix/QuickFixer.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/quickfix/QuickFixer.java @@ -10,13 +10,13 @@ *******************************************************************************/ package com.reprezen.swagedit.core.quickfix; +import static com.reprezen.swagedit.core.preferences.KaiZenPreferencesUtils.getTabWidth; + import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.dadacoalition.yedit.preferences.PreferenceConstants; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; -import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; @@ -83,10 +83,6 @@ protected String getIndent(IDocument document, int line) throws BadLocationExcep return definitionIndent + Strings.repeat(" ", getTabWidth()); } - private int getTabWidth() { - IPreferenceStore prefs = org.dadacoalition.yedit.Activator.getDefault().getPreferenceStore(); - return prefs.getInt(PreferenceConstants.SPACES_PER_TAB); - } } } diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ComplexTypeDefinition.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ComplexTypeDefinition.java index a6e8e880..aff284ce 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ComplexTypeDefinition.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ComplexTypeDefinition.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.collect.Iterables; import com.reprezen.swagedit.core.model.AbstractNode; /** @@ -139,4 +140,10 @@ public boolean validate(AbstractNode valueNode) { return isValid; } + + @Override + public String getLabel() { + TypeDefinition firstType = Iterables.getFirst(complexTypes, null); + return firstType!=null ? firstType.getLabel() : null; + } } \ No newline at end of file diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/CompositeSchema.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/CompositeSchema.java index 7d9c005a..069f260d 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/CompositeSchema.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/CompositeSchema.java @@ -138,7 +138,7 @@ protected String baseURI(String href) { return href.startsWith("#") ? null : href.split("#")[0]; } - protected JsonPointer pointer(String href) { + public static JsonPointer pointer(String href) { if (href.startsWith("#")) { return JsonPointer.compile(href.substring(1)); } else if (href.startsWith("/")) { diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/JsonSchemaUtils.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/JsonSchemaUtils.java new file mode 100644 index 00000000..283f95f7 --- /dev/null +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/JsonSchemaUtils.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2017 ModelSolv, Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * ModelSolv, Inc. - initial API and implementation and/or initial documentation + *******************************************************************************/ +package com.reprezen.swagedit.core.schema; + +import static com.google.common.collect.Iterators.transform; + +import java.util.Iterator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.base.Function; +import com.google.common.base.Joiner; + +public class JsonSchemaUtils { + public static String getHumanFriendlyText(JsonNode swaggerSchemaNode, final String defaultValue) { + String schemaTitle = getSchemaTitle(swaggerSchemaNode); + if (schemaTitle != null) { + return schemaTitle; + } + // nested array + if (swaggerSchemaNode.get("items") != null) { + return getHumanFriendlyText(swaggerSchemaNode.get("items"), defaultValue); + } + // "$ref":"#/definitions/headerParameterSubSchema" + JsonNode ref = swaggerSchemaNode.get("$ref"); + if (ref != null) { + return getLabelForRef(ref.asText()); + } + // Auxiliary oneOf in "oneOf": [ { "$ref": "#/definitions/securityRequirement" }] + JsonNode oneOf = swaggerSchemaNode.get("oneOf"); + if (oneOf != null) { + if (oneOf instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) oneOf; + if (arrayNode.size() > 0) { + Iterator labels = transform(arrayNode.elements(), new Function() { + + @Override + public String apply(JsonNode el) { + return getHumanFriendlyText(el, defaultValue); + } + }); + return "[" + Joiner.on(", ").join(labels) + "]"; + } + } + } + return defaultValue; + } + + public static String getSchemaTitle(JsonNode swaggerSchemaNode) { + JsonNode title = swaggerSchemaNode.get("title"); + if (title != null) { + return title.asText(); + } + return null; + } + + public static String getLabelForRef(String refValue) { + return refValue.substring(refValue.lastIndexOf("/") + 1); + } +} diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ReferenceTypeDefinition.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ReferenceTypeDefinition.java index 7f205044..e676289e 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ReferenceTypeDefinition.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/ReferenceTypeDefinition.java @@ -68,4 +68,18 @@ public TypeDefinition getPropertyType(String property) { public boolean validate(AbstractNode valueNode) { return resolve().validate(valueNode); } + + @Override + public String getLabel() { + String label = JsonSchemaUtils.getSchemaTitle(content); + if (label != null) { + return label; + } + // $ref=schemaOrReference + if (resolve() instanceof ComplexTypeDefinition) { + return resolve().getLabel(); + } + // $ref=schema + return JsonSchemaUtils.getLabelForRef(content.get(JsonReference.PROPERTY).asText()); + } } diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/TypeDefinition.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/TypeDefinition.java index 19c98c5b..1703cbba 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/TypeDefinition.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/schema/TypeDefinition.java @@ -73,6 +73,21 @@ public String getDescription() { } return content.get("description").asText(); } + + /** + * Human-friendly label which can be used in GUI. + * It does not intent to provide complete information about a type, + * just a concise label.
+ * Subclasses are welcome to override this method. + *
+ * Can return null. + */ + public String getLabel() { + if (content == null) { + return null; + } + return JsonSchemaUtils.getHumanFriendlyText(content, null); + } @Override public String toString() { diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/templates/ElementNameResolver.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/templates/ElementNameResolver.java new file mode 100644 index 00000000..777f93a2 --- /dev/null +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/templates/ElementNameResolver.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2016 ModelSolv, Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * ModelSolv, Inc. - initial API and implementation and/or initial documentation + *******************************************************************************/ +package com.reprezen.swagedit.core.templates; + +import java.util.List; + +import org.eclipse.jface.text.templates.TemplateContext; +import org.eclipse.jface.text.templates.TemplateVariable; +import org.eclipse.jface.text.templates.TemplateVariableResolver; + +/** + * Standard code template variables can only define values that are valid variables, e.g. no space allowed.
+ * This template variable resolver provides a way to create and select names with special characters (whitespaces, '(', ')') in code templates. For + * example, `${element_name:element_name('(schema name)')}` creates `(schema name)` and selects it for editing. + * + */ +public class ElementNameResolver extends TemplateVariableResolver { + public ElementNameResolver() { + super("element_name", "Provides human-friendly element name and selects it"); + } + + @Override + public void resolve(TemplateVariable variable, TemplateContext context) { + List params = variable.getVariableType().getParams(); + String[] bindings = new String[params.size()]; + for (int i = 0; i < params.size(); i++) { + bindings[i] = params.get(i).toString(); + } + if (bindings.length != 0) + variable.setValues(bindings); + variable.setResolved(true); + } + +} diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/templates/SchemaBasedTemplateContextType.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/templates/SchemaBasedTemplateContextType.java index 70734baa..89222102 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/templates/SchemaBasedTemplateContextType.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/templates/SchemaBasedTemplateContextType.java @@ -65,6 +65,7 @@ private void addGlobalResolvers() { addResolver(new GlobalTemplateVariables.Year()); addResolver(new GlobalTemplateVariables.Time()); addResolver(new GlobalTemplateVariables.User()); + addResolver(new ElementNameResolver()); } } diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/MultipleSwaggerErrorBuilder.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/MultipleSwaggerErrorBuilder.java index 1d7889ea..f8b77741 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/MultipleSwaggerErrorBuilder.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/MultipleSwaggerErrorBuilder.java @@ -10,20 +10,15 @@ *******************************************************************************/ package com.reprezen.swagedit.core.validation; -import static com.google.common.collect.Iterators.transform; - import java.util.Comparator; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeSet; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.google.common.base.Function; -import com.google.common.base.Joiner; import com.google.common.base.Strings; +import com.reprezen.swagedit.core.schema.JsonSchemaUtils; import com.reprezen.swagedit.core.validation.SwaggerError.MultipleSwaggerError; public class MultipleSwaggerErrorBuilder { @@ -102,45 +97,7 @@ protected String getHumanFriendlyText(String location) { if (swaggerSchemaNode == null) { return location; } - return getHumanFriendlyText(swaggerSchemaNode, location); - } - - protected String getHumanFriendlyText(JsonNode swaggerSchemaNode, final String defaultValue) { - JsonNode title = swaggerSchemaNode.get("title"); - if (title != null) { - return title.asText(); - } - // nested array - if (swaggerSchemaNode.get("items") != null) { - return getHumanFriendlyText(swaggerSchemaNode.get("items"), defaultValue); - } - // "$ref":"#/definitions/headerParameterSubSchema" - JsonNode ref = swaggerSchemaNode.get("$ref"); - if (ref != null) { - return getLabelForRef(ref.asText()); - } - // Auxiliary oneOf in "oneOf": [ { "$ref": "#/definitions/securityRequirement" }] - JsonNode oneOf = swaggerSchemaNode.get("oneOf"); - if (oneOf != null) { - if (oneOf instanceof ArrayNode) { - ArrayNode arrayNode = (ArrayNode) oneOf; - if (arrayNode.size() > 0) { - Iterator labels = transform(arrayNode.elements(), new Function() { - - @Override - public String apply(JsonNode el) { - return getHumanFriendlyText(el, defaultValue); - } - }); - return "[" + Joiner.on(", ").join(labels) + "]"; - } - } - } - return defaultValue; - } - - private String getLabelForRef(String refValue) { - return refValue.substring(refValue.lastIndexOf("/") + 1); + return JsonSchemaUtils.getHumanFriendlyText(swaggerSchemaNode, location); } } diff --git a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/SwaggerError.java b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/SwaggerError.java index 0e2fabbb..c3b3ccc2 100644 --- a/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/SwaggerError.java +++ b/com.reprezen.swagedit.core/src/com/reprezen/swagedit/core/validation/SwaggerError.java @@ -63,6 +63,11 @@ public int getLevel() { public int getLine() { return line; } + + @Override + public java.lang.String toString() { + return getMessage(); + } String getIndentedMessage() { final StringBuilder builder = new StringBuilder(); diff --git a/com.reprezen.swagedit.openapi3.tests/resources/code-assist/code-templates/SchemaObject.yaml b/com.reprezen.swagedit.openapi3.tests/resources/code-assist/code-templates/SchemaObject.yaml index 4f9f1df8..ead6de78 100644 --- a/com.reprezen.swagedit.openapi3.tests/resources/code-assist/code-templates/SchemaObject.yaml +++ b/com.reprezen.swagedit.openapi3.tests/resources/code-assist/code-templates/SchemaObject.yaml @@ -40,6 +40,15 @@ components: type: integer format: int32 minimum: 0 + + SchemaWithProperties: + type: object + required: + - name + properties: + name: + type: string + #KZOE-template name="named schema in a property", value="properties" StringToModelMappingObject: type: object diff --git a/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/CodeTemplateTextTest.xtend b/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/CodeTemplateTextTest.xtend index 0cb1ed5c..23faf78c 100644 --- a/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/CodeTemplateTextTest.xtend +++ b/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/CodeTemplateTextTest.xtend @@ -60,7 +60,16 @@ class CodeTemplateTextTest { val testCases = new CodeAssistHelper().extractTests(resourcesDir, KZOEref) testCases.forEach [ val contextType = getCodeTemplateContext(createOpenApi3Document(it.file), it.offset) - val templates = Activator.^default.templateStore.getTemplates(contextType?.id); + val activator = new Activator() { + + override protected getTabWidth() { + // YEdit preferences defaults are not iniatialized properly why running the test from Maven + // Therefore, tabWidth is 0 which creates false test failures + 2 + } + + } + val templates = activator.templateStore.getTemplates(contextType?.id); result.addAll(templates.map [ t | #[it.file, it.file.name, it.offset, it.name, t, t.name, contextType] as Object[] ]) diff --git a/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/OpenApi3ContentAssistProcessorTest.xtend b/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/OpenApi3ContentAssistProcessorTest.xtend index 3e835148..a261a491 100644 --- a/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/OpenApi3ContentAssistProcessorTest.xtend +++ b/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/assist/OpenApi3ContentAssistProcessorTest.xtend @@ -17,6 +17,7 @@ import org.junit.Test import static com.reprezen.swagedit.openapi3.utils.Cursors.* import static org.hamcrest.core.IsCollectionContaining.* +import static org.hamcrest.core.IsNot.* import static org.junit.Assert.* import com.reprezen.swagedit.openapi3.schema.OpenApi3Schema import com.reprezen.swagedit.core.model.Model @@ -34,7 +35,7 @@ class OpenApi3ContentAssistProcessorTest { } @Test - def void testCallbacksInOperation_ShouldReturnKey() { + def void testCallbacksInOperation_ShouldReturn_CallbackName() { val document = new OpenApi3Document(new OpenApi3Schema) val test = setUpContentAssistTest(''' paths: @@ -44,15 +45,14 @@ class OpenApi3ContentAssistProcessorTest { <1> ''', document) - val proposals = test.apply(processor, "1") - assertThat( - proposals.map[(it as StyledCompletionProposal).replacementString], - hasItems("_key_:") + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(callback name):") ) } @Test - def void testCallbacksInComponents_ShouldReturnKey() { + def void testCallbacksInComponents_ShouldReturn_CallbackName() { val document = new OpenApi3Document(new OpenApi3Schema) val test = setUpContentAssistTest(''' components: @@ -61,8 +61,329 @@ class OpenApi3ContentAssistProcessorTest { ''', document) val proposals = test.apply(processor, "1") - assertThat( - proposals.map[(it as StyledCompletionProposal).replacementString], + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(callback name):") + ) + } + + @Test + def void testSchemaInComponents_ShouldReturn_SchemaName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + schemas: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(schema name):") + ) + } + + @Test + def void testAnonymousSchemaInMediaType_Should_NOT_Return_SchemaName_Key() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' +paths: + /resource: + get: + description: description + responses: + default: + description: Ok + content: + application/json: + schema: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + not(hasItem("(schema name):")) + ) + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + not(hasItem("_key_:")) + ) + } + + @Test + def void testSchemaInSchemaProperties_ShouldReturn_PropertyName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + schemas: + MyDataType: + type: object + properties: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(property name):") + ) + } + + + @Test + def void testResponseInComponents_ShouldReturn_ResponseName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + responses: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(response name):") + ) + } + + + @Test + def void testParameterInComponents_ShouldReturn_ParameterName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + parameters: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(parameter name):") + ) + } + + @Test + def void testExampleInComponents_ShouldReturn_ExampleName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + examples: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(example name):") + ) + } + + @Test + def void testRequestBodiesInComponents_ShouldReturn_RequestBodyName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + requestBodies: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(requestBody name):") + ) + } + + @Test + def void testHeaderInComponents_ShouldReturn_HeaderName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + headers: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(header name):") + ) + } + + @Test + def void testSecuritySchemeInComponents_ShouldReturn_SecuritySchemeName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + securitySchemes: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(securityScheme name):") + ) + } + + @Test + def void testLinkInComponents_ShouldReturn_LinkName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' + components: + links: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(link name):") + ) + } + + @Test + def void testMediaTypeInContent_Should_NOT_Return_MediaTypeName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' +paths: + "/resource": + get: + description: description + responses: + '200': + description: Ok + content: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + // we don't need a "(mediaType name)" as valid mediatypes are provided by + // [#395] OpenAPI v3: Content assist for media types + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + not(hasItem("(mediaType name):")) + ) + } + + @Test + def void testEncodingInMediaType_ShouldReturn_EncodingName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' +paths: + "/resource": + get: + description: description + responses: + '200': + description: Ok + content: + application/json: + encoding: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(encoding name):") + ) + } + + @Test + def void testServerVariableInServer_ShouldReturn_ServerVariableName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' +servers: +- url: https://development.gigantic-server.com/v1 + description: Development server + variables: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(serverVariable name):") + ) + } + + @Test + def void testAnyInLinkParameter_ShouldReturn_AnyName() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' +components: + links: + MyLink: + operationId: getRepositoriesByOwner + parameters: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("(any name):") + ) + } + + @Test + def void testStringsInDiscriminatorMapping() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' +components: + schemas: + MyResponseType: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + discriminator: + propertyName: pet_type + mapping: + <1> + ''', document) + + val proposals = test.apply(processor, "1") + // _key_ is a temporary solution, just documenting the current state + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("_key_:") + ) + } + + @Test + def void testStringsInOauthFlowScopes() { + val document = new OpenApi3Document(new OpenApi3Schema) + val test = setUpContentAssistTest(''' +components: + securitySchemes: + mySecurityScheme: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/api/oauth/dialog + scopes: + <1> + ''', document) + + // _key_ is a temporary solution, just documenting the current state + val proposals = test.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], + hasItems("_key_:") + ) + } + + @Test + def void testResponseSchemaStillContainsKeyProposal() { + val document = setUpContentAssistTest(''' + openapi: "3.0.0" + info: + version: 1.0.0 + title: Test + paths: + /: + get: + summary: All + responses: + 200: + description: Ok + content: + application/json: + schema: + <1>$ref: "#/components/schemas/Pets" + components: + schemas: + Pets: + type: object + ''', new OpenApi3Document(new OpenApi3Schema)) + + val proposals = document.apply(processor, "1") + assertThat(proposals.map[(it as StyledCompletionProposal).replacementString], hasItems("_key_:") ) } diff --git a/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/validation/ValidationHelper.xtend b/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/validation/ValidationHelper.xtend index d3ac24da..331babc9 100644 --- a/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/validation/ValidationHelper.xtend +++ b/com.reprezen.swagedit.openapi3.tests/src/com/reprezen/swagedit/openapi3/validation/ValidationHelper.xtend @@ -33,7 +33,7 @@ class ValidationHelper { val JsonNode schemaAsJson = getSchema().asJson() val ErrorProcessor processor = new ErrorProcessor(null, null) { override protected Set fromNode(JsonNode error, int indent) { - fail('''JSON Schema validation error: «error.asText()»''') + fail('''JSON Schema validation error: «super.fromNode(error, indent)»''') return super.fromNode(error, indent) } } diff --git a/com.reprezen.swagedit.openapi3/resources/templates.xml b/com.reprezen.swagedit.openapi3/resources/templates.xml index a9c93c7a..f4eb8cef 100644 --- a/com.reprezen.swagedit.openapi3/resources/templates.xml +++ b/com.reprezen.swagedit.openapi3/resources/templates.xml @@ -272,21 +272,9 @@ content: $$ref: ${model} - - - + @@ -343,6 +331,14 @@ properties: properties: ${name}: type: string + + + @@ -390,5 +386,4 @@ properties: 'text/html': schema: $$ref: '${errorModel}' - \ No newline at end of file diff --git a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/Activator.java b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/Activator.java index 169200a4..0acae183 100644 --- a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/Activator.java +++ b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/Activator.java @@ -14,12 +14,16 @@ import org.dadacoalition.yedit.YEditLog; import org.eclipse.jface.text.templates.ContextTypeRegistry; +import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateContextType; +import org.eclipse.jface.text.templates.persistence.TemplatePersistenceData; import org.eclipse.jface.text.templates.persistence.TemplateStore; import org.eclipse.ui.editors.text.templates.ContributionTemplateStore; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; +import com.google.common.base.Strings; +import com.reprezen.swagedit.core.preferences.KaiZenPreferencesUtils; import com.reprezen.swagedit.openapi3.schema.OpenApi3Schema; import com.reprezen.swagedit.openapi3.templates.OpenApi3ContextTypeProvider; @@ -106,8 +110,45 @@ public TemplateStore getTemplateStore() { } catch (IOException e) { YEditLog.logException(e); } + addNamedSchemaTemplatesInSchemas(); + addNamedSchemaTemplatesInSchemaProperties(); } return templateStore; } + private void addNamedSchemaTemplatesInSchemas() { + addNamedTemplates("com.reprezen.swagedit.openapi3.templates.schema", + "com.reprezen.swagedit.openapi3.templates.schemas", "schema"); + } + + private void addNamedSchemaTemplatesInSchemaProperties() { + addNamedTemplates("com.reprezen.swagedit.openapi3.templates.schema", + "com.reprezen.swagedit.openapi3.templates.properties", "property"); + } + + private void addNamedTemplates(String inlineContextId, String namedContextId, String key) { + Template[] schemaTemplates = templateStore.getTemplates(inlineContextId); + for (int i = 0; i < schemaTemplates.length; i++) { + Template schemaTemplate = schemaTemplates[i]; + Template template = createNamedTemplate(schemaTemplate, namedContextId, key); + templateStore.add(new TemplatePersistenceData(template, true)); + } + } + + private Template createNamedTemplate(Template inlineTemplate, String newTemplateId, String key) { + String indent = Strings.repeat(" ", getTabWidth()); + String newPattern = inlineTemplate.getPattern().replaceAll("\n", "\n" + indent); + String pattern = String.format("${element_name:element_name('(%s name)')}:\n%s%s", key, indent, newPattern); + Template template = new Template(inlineTemplate.getName(), // + inlineTemplate.getDescription(), // + newTemplateId, // + pattern, // + inlineTemplate.isAutoInsertable()); + return template; + } + + protected int getTabWidth() { + return KaiZenPreferencesUtils.getTabWidth(); + } + } diff --git a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/assist/ext/CallbacksContentAssistExt.java b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/assist/ext/CallbacksContentAssistExt.java index f93043c0..e25f17b1 100644 --- a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/assist/ext/CallbacksContentAssistExt.java +++ b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/assist/ext/CallbacksContentAssistExt.java @@ -31,7 +31,6 @@ public boolean canProvideContentAssist(TypeDefinition type) { @Override public Collection getProposals(TypeDefinition type, AbstractNode node, String prefix) { return Lists.newArrayList( // - new Proposal("_key_:", "_key_", null, "callbackOrReference"), // new Proposal("x-:", "x-", null, "specificationExtension")); } diff --git a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/schema/schema_v3.json b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/schema/schema_v3.json index 6167ad25..4e9e44cd 100644 --- a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/schema/schema_v3.json +++ b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/schema/schema_v3.json @@ -3,7 +3,7 @@ "id": "http://openapis.org/v3/schema.json#", "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "description": "This is the root document object of the OpenAPI definition.", + "description": "This is the root document object of the OpenAPI document.", "required": [ "openapi", "info", @@ -834,6 +834,7 @@ ] }, "properties": { + "title": "property", "type": "object", "additionalProperties": { "$ref": "#/definitions/schemaOrReference" diff --git a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/templates/OpenApi3ContextTypeProvider.java b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/templates/OpenApi3ContextTypeProvider.java index 9f1f59ef..105424c1 100644 --- a/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/templates/OpenApi3ContextTypeProvider.java +++ b/com.reprezen.swagedit.openapi3/src/com/reprezen/swagedit/openapi3/templates/OpenApi3ContextTypeProvider.java @@ -84,7 +84,10 @@ private SchemaBasedTemplateContextType createOpenApi3TemplateContextType(String createOpenApi3TemplateContextType("securityScheme", "/definitions/securityScheme"), createOpenApi3TemplateContextType("header", "/definitions/header"), // Other - createOpenApi3TemplateContextType("mediaTypes", "/definitions/mediaTypes")); + createOpenApi3TemplateContextType("mediaTypes", "/definitions/mediaTypes"), + createOpenApi3TemplateContextType("properties", "/definitions/schema/properties/type", + "/definitions/schema/properties/properties")// + ); public List allContextTypes() { return allContextTypes; diff --git a/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/assist/SwaggerProposalProviderTest.xtend b/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/assist/SwaggerProposalProviderTest.xtend index b9a383bf..18bb4fd4 100644 --- a/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/assist/SwaggerProposalProviderTest.xtend +++ b/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/assist/SwaggerProposalProviderTest.xtend @@ -114,9 +114,21 @@ class SwaggerProposalProviderTest { assertThat(provider.getProposals(node).map [ replacementString ], hasItems( - "_key_:" + "(schema name):" )) } + + @Test + def void testGetProposals_SchemaPropertiesObject() { + val node = model.objectNode(null, "/definitions/MyType/properties".ptr) + node.type = schema.getType(node) + + assertThat(provider.getProposals(node).map [ + replacementString + ], hasItems( + "(property name):" + )) + } @Test def void testPathGetProposals() { diff --git a/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/validation/MultipleSwaggerErrorMessageTest.xtend b/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/validation/MultipleSwaggerErrorMessageTest.xtend index d6e569e4..ec174023 100644 --- a/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/validation/MultipleSwaggerErrorMessageTest.xtend +++ b/com.reprezen.swagedit.tests/src/com/reprezen/swagedit/validation/MultipleSwaggerErrorMessageTest.xtend @@ -4,13 +4,13 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.node.ArrayNode import com.google.common.base.Strings import com.google.common.collect.Lists +import com.reprezen.swagedit.core.schema.JsonSchemaUtils import com.reprezen.swagedit.schema.SwaggerSchema import io.swagger.util.Json import java.util.List import org.junit.Test import static org.junit.Assert.* -import com.reprezen.swagedit.core.validation.MultipleSwaggerErrorBuilder class MultipleSwaggerErrorMessageTest { @@ -75,28 +75,14 @@ class MultipleSwaggerErrorMessageTest { } def void assertHumanFriendlyTextForNodeEquals(CharSequence json, String expectedLabel, String defaultValue) { - val swaggerError = new MultipleSwaggerErrorBuilder() { - - def public public_getHumanFriendlyText(JsonNode swaggerSchemaNode, String defaultValue) { - super.getHumanFriendlyText(swaggerSchemaNode, defaultValue) - } - - }; val JsonNode arrayOfSchemasNode = Json.mapper().readTree(json.toString); assertNotNull(arrayOfSchemasNode) - val label = swaggerError.public_getHumanFriendlyText(arrayOfSchemasNode, defaultValue); + val label = JsonSchemaUtils::getHumanFriendlyText(arrayOfSchemasNode, defaultValue); assertEquals(expectedLabel, label) } def void testCombinedSchemas(String propertyName) throws Exception { val JsonNode swaggerSchema = new SwaggerSchema().asJson - val swaggerError = new MultipleSwaggerErrorBuilder() { - - def public public_getHumanFriendlyText(JsonNode swaggerSchemaNode, String defaultValue) { - super.getHumanFriendlyText(swaggerSchemaNode, defaultValue) - } - - }; val List combinedSchemas = newArrayList(); // oneOf and anyOf are usually ArrayNodes swaggerSchema.findValues(propertyName).forEach [ @@ -108,7 +94,7 @@ class MultipleSwaggerErrorMessageTest { ]; assertFalse(combinedSchemas.filterNull.isNullOrEmpty) - val emptyLabels = combinedSchemas.filter[it|Strings.isNullOrEmpty(swaggerError.public_getHumanFriendlyText(it, null))] + val emptyLabels = combinedSchemas.filter[it|Strings.isNullOrEmpty(JsonSchemaUtils::getHumanFriendlyText(it, null))] assertTrue("Null labels are not expected, but got null for the following nodes: " + emptyLabels, emptyLabels.isNullOrEmpty) } diff --git a/com.reprezen.swagedit/src/com/reprezen/swagedit/schema/schema.json b/com.reprezen.swagedit/src/com/reprezen/swagedit/schema/schema.json index c87c4fa0..361a8e77 100644 --- a/com.reprezen.swagedit/src/com/reprezen/swagedit/schema/schema.json +++ b/com.reprezen.swagedit/src/com/reprezen/swagedit/schema/schema.json @@ -1025,6 +1025,7 @@ }, "properties": { "type": "object", + "title": "property", "additionalProperties": { "$ref": "#/definitions/schema" },