Skip to content

Commit

Permalink
feat(core): Initial GET on CLI (re. #72)
Browse files Browse the repository at this point in the history
  • Loading branch information
vorburger committed Apr 6, 2023
1 parent 0504756 commit a0441b5
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 29 deletions.
16 changes: 12 additions & 4 deletions ToDo.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
1. `MarkdownGenerator.java` add Mermaid (like in `demo-model-docgen.md`)
1. `MarkdownGenerator.java` https://github.com/google/closure-templates/issues/1300 ?

1. Enforce https://www.conventionalcommits.org like git commit messages
1. https://github.com/enola-dev/enola/issues/102 :
Enforce https://www.conventionalcommits.org like git commit messages
starting with feat/model/fix/build/docs/clean/format/refactor: and core/k8s/tools:
using https://github.com/jorisroovers/gitlint

Expand Down Expand Up @@ -82,11 +83,18 @@

1. [Run `mkdocs build` instead of in `build.bash` as a `sh_test` in Bazel with `docs/**` + `mkdocs.yaml` as (only) `srcs`](https://github.com/enola-dev/enola/compare/main...vorburger:enola:mkdocs_build_test) - fix weird problems

1. Proto design: Is this a real requirement, or can we forget about this: _"Note that IDs are not "unique", and 2 different IDs may refer to the same underlying object; for example: `k8s:pod?name=echoserver-6dfb6c7764-45gvk&...` and `k8s:pod?uid=561f1bec-f768-4c5b-b96e-37306d7f2f8a&...`"_
1. Proto design: Is this a real requirement, or can we forget about this: _"Note that IDs are not "unique", and 2
different IDs may refer to the same underlying object; for example: `k8s:pod?name=echoserver-6dfb6c7764-45gvk&...`
and `k8s:pod?uid=561f1bec-f768-4c5b-b96e-37306d7f2f8a&...`"_

1. Proto design: Should `EntityKind` have a _"param_parent, to allow grouping common ones, just during declaration in textproto, but inlined for use."_ or shall we forget about that?
1. Proto design: Should `EntityKind` have a _"param_parent, to allow grouping common ones, just during declaration in
textproto, but inlined for use."_ or shall we forget about that?

1. Proto design: Should we permit RPC clients to _"specify the ID in either (oneof)string text or "broken down" parts form. This is simply for dev convenience in UX such as CLI or Web UIs, and to avoid the proliferation of incompatible parsers. The implementation validates the text, and rejects e.g. "demo:foo?bad=a=b" or "demo:foo?bad=a&bad=b". The string text oneof form is NOT "decoded" like un-escaped (incl. un-quoted) at all, simply "split" ... ? There was `IDsTest.java` and `IDs.java` code related to this which I removed on 2023-03-19:_
1. Proto design: Should we permit RPC clients to _"specify the ID in either (oneof)string text or "broken down" parts
form. This is simply for dev convenience in UX such as CLI or Web UIs, and to avoid the proliferation of incompatible
parsers. The implementation validates the text, and rejects e.g. "demo:foo?bad=a=b" or "demo:foo?bad=a&bad=b". The
string text oneof form is NOT "decoded" like un-escaped (incl. un-quoted) at all, simply "split" ... ? There
was `IDsTest.java` and `IDs.java` code related to this which I removed on 2023-03-19:_

message ID {
oneof oneof {
Expand Down
2 changes: 2 additions & 0 deletions cli/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ java_binary(
main_class = "dev.enola.cli.Enola",
deps = [
"//common/common",
"//core/lib:core_proto_java_library",
"//core/lib:lib_java",
"//core/impl",
"@maven//:com_google_guava_guava",
"@maven//:info_picocli_picocli",
Expand Down
3 changes: 2 additions & 1 deletion cli/src/main/java/dev/enola/cli/Enola.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
subcommands = {
HelpCommand.class, // TODO , Version.class
AutoComplete.GenerateCompletion.class,
DocGen.class
DocGen.class,
Get.class
})
public class Enola {

Expand Down
58 changes: 58 additions & 0 deletions cli/src/main/java/dev/enola/cli/Get.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2023 The Enola <https://enola.dev> Authors
*
* 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
*
* https://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 dev.enola.cli;

import dev.enola.core.EnolaServiceRegistry;
import dev.enola.core.IDs;
import dev.enola.core.meta.EntityKindRepository;
import dev.enola.core.proto.GetEntityRequest;
import dev.enola.core.proto.ID;

import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Spec;

@CommandLine.Command(name = "get", description = "Get Entity")
public class Get extends CommandWithModel {

// TODO @Parameters(index = "0..*") List<String> ids;
// TODO ID instead of String, with https://picocli.info/#_strongly_typed_everything
@Parameters(index = "0", paramLabel = "id", description = "ID of Entity")
String idString;

@Spec CommandSpec spec;

@Override
protected void run(EntityKindRepository ekr) throws Exception {
// TODO Move this from CLI to Core?
EnolaServiceRegistry service = new EnolaServiceRegistry();
// TODO registry.register(...);

ID id = IDs.parse(idString); // TODO replace with ITypeConverter
// TODO Validate id; here it must have ns+name+path!

var request = GetEntityRequest.newBuilder().setId(id).build();
var response = service.getEntity(request);
var entity = response.getEntity();

// TODO Allow formatting it as JSON or YAML, via Resource!
// TODO What else do we do with the Entity?!
spec.commandLine().getOut().println(entity);
}
}
13 changes: 13 additions & 0 deletions cli/src/test/java/dev/enola/cli/EnolaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,17 @@ public void testDocGen() throws IOException {
Truth.assertThat(r.charSource().read()).endsWith(MarkdownDocGenerator.FOOTER);
}
}

@Test
public void testGet() {
var exec =
cli(
"-v",
"get",
"--model",
"classpath:cli-test-model.textproto",
"test:first/helo");
assertThat(exec).err().isEmpty();
assertThat(exec).hasExitCode(0).out().isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.net.URI;

class EmptyResource implements ReadableResource {
// TODO Perhaps rename this to VoidResource with void:/ URI?

static final EmptyResource INSTANCE = new EmptyResource();

Expand Down
53 changes: 53 additions & 0 deletions core/impl/src/main/java/dev/enola/core/EnolaServiceRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2023 The Enola <https://enola.dev> Authors
*
* 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
*
* https://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 dev.enola.core;

import dev.enola.core.proto.GetEntityRequest;
import dev.enola.core.proto.GetEntityResponse;
import dev.enola.core.proto.ID;

import java.util.HashMap;
import java.util.Map;

public class EnolaServiceRegistry implements EnolaService {

private final Map<ID, EnolaService> registry;

public EnolaServiceRegistry() {
registry = new HashMap<>();
}

public void register(ID id, EnolaService service) {
var lookup = IDs.withoutPath(id);
var existing = registry.get(lookup);
if (existing != null) {
throw new IllegalArgumentException("Service already registered for: " + lookup);
}
registry.put(lookup, service);
}

@Override
public GetEntityResponse getEntity(GetEntityRequest r) {
var lookup = IDs.withoutPath(r.getId());
var delegate = registry.get(lookup);
if (delegate == null) {
throw new IllegalArgumentException("No Connector registered for: " + lookup);
}
return delegate.getEntity(r);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void render(EntityKindRepository kinds, Appendable md) throws IOException
}

private void render(EntityKind ek, Appendable md) throws IOException {
ID idWithoutPathArguments = ID.newBuilder(ek.getId()).clearPaths().build();
ID idWithoutPathArguments = IDs.withoutPath(ek.getId());
String fqn = IDs.toPath(idWithoutPathArguments);
List<String> pathArguments =
ek.getId().getPathsList().stream().collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ static void renderMermaid(EntityKindRepository kinds, Appendable md) throws IOEx
}

private static void renderMermaidEntity(EntityKind ek, Appendable md) throws IOException {
ID idWithoutPathArguments = ID.newBuilder(ek.getId()).clearPaths().build();
ID idWithoutPathArguments = IDs.withoutPath(ek.getId());
String fqn = IDs.toPath(idWithoutPathArguments);
String name = StringUtil.capitalize(idWithoutPathArguments.getEntity());
List<String> pathArguments =
Expand Down
2 changes: 2 additions & 0 deletions core/lib/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ java_proto_library(
"util_proto",
],
visibility = [
"//cli:__subpackages__",
"//connectors:__subpackages__",
"//core:__subpackages__",
],
Expand Down Expand Up @@ -98,6 +99,7 @@ java_library(
# TODO Required for https://github.com/google/closure-templates/issues/1300 or not?
# resources = glob(["src/main/java/**/*.proto"]),
visibility = [
"//cli:__subpackages__",
"//connectors:__subpackages__",
"//core:__subpackages__",
],
Expand Down
13 changes: 9 additions & 4 deletions core/lib/src/main/java/dev/enola/core/IDs.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@
*/
public final class IDs {

private IDs() {}

private static final String URI_SCHEME = "enola";
private static final Splitter SLASH_SPLITTER =
Splitter.on('/').omitEmptyStrings().trimResults();

// private static final Joiner AMPERSAND_JOINER = Joiner.on('&').skipNulls();

private static final Splitter SLASH_SPLITTER =
Splitter.on('/').omitEmptyStrings().trimResults();
private IDs() {}

public static ID parse(String s) {
if (s.startsWith(URI_SCHEME)) {
Expand Down Expand Up @@ -196,4 +195,10 @@ public static String toQueryURI(ID id) {
return sb.toString();
}
*/

public static ID withoutPath(ID id) {
if (id == null) return null;
if (id.getPathsCount() == 0) return id;
return ID.newBuilder(id).clearPaths().build();
}
}
23 changes: 13 additions & 10 deletions core/lib/src/main/java/dev/enola/core/enola_core.proto
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,25 @@ message GetEntityRequest {

// Request to inline Entity#link, by the same string key as used there.
// (Conceptually inspired by e.g. GraphQL architecture.)
repeated string inline_links = 2;
// TODO repeated string inline_links = 2;
}
message GetEntityResponse {
Entity entity = 1;

repeated InlineLinkData data = 2;
message InlineLinkData {
oneof oneof {
google.protobuf.Any protos = 1;
string json = 2;
}
}
/*
TODO Think this through & implement it...
repeated InlineLinkData data = 2;
message InlineLinkData {
oneof oneof {
google.protobuf.Any protos = 1;
string json = 2;
}
} */
}

// TODO Later have an RPC for a time series XRequest, and XResponse with
// repeated Entity?
// TODO rpc watch(), for a time series WatchRequest/Response with repeated
// Entity?
// google.protobuf.Timestamp ts = 2;
// google.protobuf.Timestamp start = 2;
// google.protobuf.Timestamp end = 3;
Expand Down
16 changes: 8 additions & 8 deletions core/lib/src/main/java/dev/enola/core/meta/enola_meta.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ option java_string_check_utf8 = true;
option java_package = "dev.enola.core.meta.proto";
option java_multiple_files = true;

message MetaModelPackage {
// Scheme, as-in ID.parts.scheme; in lower case and without any spaces.
// TODO Enable this, with validation that entities don't have it, and
// "complete" it on read string package = 1;

// List of entities kinds of this model.
repeated EntityKind entities = 2;
}
// message MetaModelPackage {
// Scheme, as-in ID.parts.scheme; in lower case and without any spaces.
// TODO Enable this, with validation that entities don't have it, and
// "complete" it on read string package = 1;

// List of entities kinds of this model.
// repeated EntityKind entities = 2;
// }

message EntityKinds {
repeated EntityKind kinds = 1;
Expand Down
14 changes: 14 additions & 0 deletions core/lib/src/test/java/dev/enola/core/IDsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import static com.google.common.truth.Truth.assertThat;

import static dev.enola.core.IDs.withoutPath;

import static org.junit.Assert.assertThrows;

import dev.enola.core.proto.ID;
Expand Down Expand Up @@ -99,4 +101,16 @@ public void testParseBad() {
private void badEnolaID(String id) {
assertThrows(IllegalArgumentException.class, () -> IDs.parse(id));
}

@Test
public void testWithoutPath() {
assertThat(withoutPath(null)).isNull();
assertThat(withoutPath(ID.getDefaultInstance())).isEqualTo(ID.getDefaultInstance());

var id1 = ID.newBuilder().setNs("foo").setEntity("bar").build();
assertThat(withoutPath(id1)).isSameInstanceAs(id1);

var id2 = ID.newBuilder(id1).addPaths("name").addPaths("uuid").build();
assertThat(withoutPath(id2)).isEqualTo(id1);
}
}

0 comments on commit a0441b5

Please sign in to comment.