Skip to content

Commit

Permalink
structurizr-dsl: Adds support for url, properties, and `perspecti…
Browse files Browse the repository at this point in the history
…ves` nested inside `!elements` and `!relationships`.
  • Loading branch information
simonbrowndotje committed Oct 6, 2024
1 parent b412261 commit b507b5d
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 117 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- structurizr-dsl: Anonymous identifiers for relationships (i.e. relationships not assigned to an identifier) are excluded from the model, and therefore also excluded from the serialised JSON.
- structurizr-dsl: Adds a way to configure whether the DSL source is retained via a workspace property named `structurizr.dsl.source` - `true` (default) or `false`.
- structurizr-dsl: Adds the ability to define a PlantUML/Mermaid image view that is an export of a workspace view.
- structurizr-dsl: Adds support for `url`, `properties`, and `perspectives` nested inside `!elements` and `!relationships`.

## 3.0.0 (19th September 2024)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ Set<ModelItem> getModelItems() {

@Override
protected String[] getPermittedTokens() {
return new String[0];
return new String[] {
StructurizrDslTokens.RELATIONSHIP_TOKEN,
StructurizrDslTokens.TAG_TOKEN,
StructurizrDslTokens.TAGS_TOKEN,
StructurizrDslTokens.URL_TOKEN,
StructurizrDslTokens.PROPERTIES_TOKEN,
StructurizrDslTokens.PERSPECTIVES_TOKEN
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ final class ModelItemParser extends AbstractParser {

private final static int URL_INDEX = 1;

private final static int PERSPECTIVE_NAME_INDEX = 0;
private final static int PERSPECTIVE_DESCRIPTION_INDEX = 1;
private final static int PERSPECTIVE_VALUE_INDEX = 2;

void parseTags(ModelItemDslContext context, Tokens tokens) {
// tags <tags> [tags]
if (!tokens.includes(TAGS_INDEX)) {
Expand Down Expand Up @@ -52,26 +48,4 @@ void parseUrl(ModelItemDslContext context, Tokens tokens) {
context.getModelItem().setUrl(url);
}

void parsePerspective(ModelItemPerspectivesDslContext context, Tokens tokens) {
// <name> <description> [value]

if (tokens.hasMoreThan(PERSPECTIVE_VALUE_INDEX)) {
throw new RuntimeException("Too many tokens, expected: <name> <description> [value]");
}

if (!tokens.includes(PERSPECTIVE_DESCRIPTION_INDEX)) {
throw new RuntimeException("Expected: <name> <description> [value]");
}

String name = tokens.get(PERSPECTIVE_NAME_INDEX);
String description = tokens.get(PERSPECTIVE_DESCRIPTION_INDEX);
String value = "";

if (tokens.includes(PERSPECTIVE_VALUE_INDEX)) {
value = tokens.get(PERSPECTIVE_VALUE_INDEX);
}

context.getModelItem().addPerspective(name, description, value);
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
final class ModelItemsParser extends AbstractParser {

private final static int TAGS_INDEX = 1;
private final static int URL_INDEX = 1;

void parseTags(ModelItemsDslContext context, Tokens tokens) {
// tags <tags> [tags]
Expand All @@ -21,4 +22,20 @@ void parseTags(ModelItemsDslContext context, Tokens tokens) {
}
}

void parseUrl(ModelItemsDslContext context, Tokens tokens) {
// url <url>
if (tokens.hasMoreThan(URL_INDEX)) {
throw new RuntimeException("Too many tokens, expected: url <url>");
}

if (!tokens.includes(URL_INDEX)) {
throw new RuntimeException("Expected: url <url>");
}

String url = tokens.get(URL_INDEX);
for (ModelItem modelItem : context.getModelItems()) {
modelItem.setUrl(url);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.structurizr.dsl;

import com.structurizr.model.ModelItem;

final class PerspectiveParser extends AbstractParser {

private final static int PERSPECTIVE_NAME_INDEX = 0;
private final static int PERSPECTIVE_DESCRIPTION_INDEX = 1;
private final static int PERSPECTIVE_VALUE_INDEX = 2;

void parse(PerspectivesDslContext context, Tokens tokens) {
// <name> <description> [value]

if (tokens.hasMoreThan(PERSPECTIVE_VALUE_INDEX)) {
throw new RuntimeException("Too many tokens, expected: <name> <description> [value]");
}

if (!tokens.includes(PERSPECTIVE_DESCRIPTION_INDEX)) {
throw new RuntimeException("Expected: <name> <description> [value]");
}

String name = tokens.get(PERSPECTIVE_NAME_INDEX);
String description = tokens.get(PERSPECTIVE_DESCRIPTION_INDEX);
String value = "";

if (tokens.includes(PERSPECTIVE_VALUE_INDEX)) {
value = tokens.get(PERSPECTIVE_VALUE_INDEX);
}

for (ModelItem modelItem : context.getModelItems()) {
modelItem.addPerspective(name, description, value);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.structurizr.dsl;

import com.structurizr.model.ModelItem;

import java.util.ArrayList;
import java.util.Collection;

final class PerspectivesDslContext extends DslContext {

private final Collection<ModelItem> modelItems = new ArrayList<>();

PerspectivesDslContext(ModelItem modelItem) {
this.modelItems.add(modelItem);
}

PerspectivesDslContext(Collection<ModelItem> modelItems) {
this.modelItems.addAll(modelItems);
}

Collection<ModelItem> getModelItems() {
return this.modelItems;
}

@Override
protected String[] getPermittedTokens() {
return new String[0];
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@

import com.structurizr.PropertyHolder;

import java.util.ArrayList;
import java.util.Collection;

final class PropertiesDslContext extends DslContext {

private PropertyHolder propertyHolder;
private final Collection<PropertyHolder> propertyHolders = new ArrayList<>();

public PropertiesDslContext(PropertyHolder propertyHolder) {
this.propertyHolder = propertyHolder;
this.propertyHolders.add(propertyHolder);
}

public PropertiesDslContext(Collection<PropertyHolder> propertyHolders) {
this.propertyHolders.addAll(propertyHolders);
}

PropertyHolder getPropertyHolder() {
return this.propertyHolder;
Collection<PropertyHolder> getPropertyHolders() {
return this.propertyHolders;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.structurizr.dsl;

import com.structurizr.PropertyHolder;

final class PropertyParser extends AbstractParser {

private final static int PROPERTY_NAME_INDEX = 0;
Expand All @@ -19,7 +21,9 @@ void parse(PropertiesDslContext context, Tokens tokens) {
String name = tokens.get(PROPERTY_NAME_INDEX);
String value = tokens.get(PROPERTY_VALUE_INDEX);

context.getPropertyHolder().addProperty(name, value);
for (PropertyHolder propertyHolder : context.getPropertyHolders()) {
propertyHolder.addProperty(name, value);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ Set<ModelItem> getModelItems() {

@Override
protected String[] getPermittedTokens() {
return new String[0];
return new String[] {
StructurizrDslTokens.TAG_TOKEN,
StructurizrDslTokens.TAGS_TOKEN,
StructurizrDslTokens.URL_TOKEN,
StructurizrDslTokens.PROPERTIES_TOKEN,
StructurizrDslTokens.PERSPECTIVES_TOKEN
};
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.structurizr.dsl;

import com.structurizr.PropertyHolder;
import com.structurizr.Workspace;
import com.structurizr.model.*;
import com.structurizr.util.StringUtils;
Expand Down Expand Up @@ -554,6 +555,9 @@ void parse(List<String> lines, File dslFile, boolean fragment, boolean includeIn
} else if (URL_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemDslContext.class) && !isGroup(getContext())) {
new ModelItemParser().parseUrl(getContext(ModelItemDslContext.class), tokens);

} else if (URL_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemsDslContext.class)) {
new ModelItemsParser().parseUrl(getContext(ModelItemsDslContext.class), tokens);

} else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
startContext(new PropertiesDslContext(workspace));

Expand All @@ -566,6 +570,9 @@ void parse(List<String> lines, File dslFile, boolean fragment, boolean includeIn
} else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemDslContext.class) && !isGroup(getContext())) {
startContext(new PropertiesDslContext(getContext(ModelItemDslContext.class).getModelItem()));

} else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemsDslContext.class)) {
startContext(new PropertiesDslContext(getContext(ModelItemsDslContext.class).getModelItems().stream().map(mi -> (PropertyHolder)mi).toList()));

} else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
startContext(new PropertiesDslContext(workspace.getViews().getConfiguration()));

Expand All @@ -585,10 +592,13 @@ void parse(List<String> lines, File dslFile, boolean fragment, boolean includeIn
new PropertyParser().parse(getContext(PropertiesDslContext.class), tokens);

} else if (PERSPECTIVES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemDslContext.class) && !isGroup(getContext())) {
startContext(new ModelItemPerspectivesDslContext(getContext(ModelItemDslContext.class).getModelItem()));
startContext(new PerspectivesDslContext(getContext(ModelItemDslContext.class).getModelItem()));

} else if (PERSPECTIVES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemsDslContext.class)) {
startContext(new PerspectivesDslContext(getContext(ModelItemsDslContext.class).getModelItems()));

} else if (inContext(ModelItemPerspectivesDslContext.class)) {
new ModelItemParser().parsePerspective(getContext(ModelItemPerspectivesDslContext.class), tokens);
} else if (inContext(PerspectivesDslContext.class)) {
new PerspectiveParser().parse(getContext(PerspectivesDslContext.class), tokens);

} else if (WORKSPACE_TOKEN.equalsIgnoreCase(firstToken) && contextStack.empty()) {
if (parsedTokens.contains(WORKSPACE_TOKEN)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.structurizr.dsl;

import com.structurizr.model.Perspective;
import com.structurizr.model.SoftwareSystem;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -105,61 +104,4 @@ void test_parseUrl_SetsTheUrl_WhenAUrlIsSpecified() {
assertEquals("http://example.com", softwareSystem.getUrl());
}

@Test
void test_parsePerspective_ThrowsAnException_WhenThereAreTooManyTokens() {
try {
ModelItemPerspectivesDslContext context = new ModelItemPerspectivesDslContext(null);
parser.parsePerspective(context, tokens("name", "description", "value", "extra"));
fail();
} catch (Exception e) {
assertEquals("Too many tokens, expected: <name> <description> [value]", e.getMessage());
}
}

@Test
void test_parsePerspective_ThrowsAnException_WhenNoNameIsSpecified() {
try {
SoftwareSystem softwareSystem = model.addSoftwareSystem("Name", "Description");
ModelItemPerspectivesDslContext context = new ModelItemPerspectivesDslContext(softwareSystem);
parser.parsePerspective(context, tokens());
fail();
} catch (Exception e) {
assertEquals("Expected: <name> <description> [value]", e.getMessage());
}
}

@Test
void test_parsePerspective_ThrowsAnException_WhenNoDescriptionIsSpecified() {
try {
SoftwareSystem softwareSystem = model.addSoftwareSystem("Name", "Description");
ModelItemPerspectivesDslContext context = new ModelItemPerspectivesDslContext(softwareSystem);
parser.parsePerspective(context, tokens("name"));
fail();
} catch (Exception e) {
assertEquals("Expected: <name> <description> [value]", e.getMessage());
}
}

@Test
void test_parsePerspective_AddsThePerspective_WhenADescriptionIsSpecified() {
SoftwareSystem softwareSystem = model.addSoftwareSystem("Name", "Description");
ModelItemPerspectivesDslContext context = new ModelItemPerspectivesDslContext(softwareSystem);
parser.parsePerspective(context, tokens("Security", "Description"));

Perspective perspective = softwareSystem.getPerspectives().stream().filter(p -> p.getName().equals("Security")).findFirst().get();
assertEquals("Description", perspective.getDescription());
assertEquals("", perspective.getValue());
}

@Test
void test_parsePerspective_AddsThePerspective_WhenADescriptionAndValueIsSpecified() {
SoftwareSystem softwareSystem = model.addSoftwareSystem("Name", "Description");
ModelItemPerspectivesDslContext context = new ModelItemPerspectivesDslContext(softwareSystem);
parser.parsePerspective(context, tokens("Security", "Description", "Value"));

Perspective perspective = softwareSystem.getPerspectives().stream().filter(p -> p.getName().equals("Security")).findFirst().get();
assertEquals("Description", perspective.getDescription());
assertEquals("Value", perspective.getValue());
}

}
Loading

0 comments on commit b507b5d

Please sign in to comment.