From ac9258762fe66d39bc6ad612eee3ee775a0361a5 Mon Sep 17 00:00:00 2001 From: Michael Vorburger Date: Sun, 3 Dec 2023 00:06:59 +0100 Subject: [PATCH] WIP Next Phase... This includes only indirectly related work, such as: * Add check-jsonschema pre-commit --- .bazelrc | 1 + .devcontainer/devcontainer.json | 16 +- .editorconfig | 3 + .pre-commit-config.yaml | 39 +- .vscode/settings.json | 42 +- .yamllint.yaml | 21 + .../dev/enola/core/AbstractRepository.java | 20 + .../dev/enola/core/EnolaServiceProvider.java | 19 +- .../dev/enola/core/type/TypeRepository.java | 78 ++ .../enola/core/meta/TypeRepositoryTest.java | 36 + core/impl/src/test/resources/test-types.yaml | 41 + core/lib/BUILD | 31 +- .../src/main/java/dev/enola/core/ByteSeq.java | 168 ++++ .../main/java/dev/enola/core/enola_core.proto | 3 +- .../main/java/dev/enola/core/enola_ext.proto | 33 + .../java/dev/enola/core/meta/enola_meta.proto | 145 +++- .../test/java/dev/enola/core/ByteSeqTest.java | 80 ++ docs/concepts/core.md | 10 +- docs/concepts/uri.md | 4 +- docs/dev/setup.md | 11 +- docs/models/demo/hello/README.md | 92 ++ docs/models/demo/hello/greeting.proto | 23 + docs/models/demo/hello/greeting.type.yaml | 22 + docs/models/demo/hello/hello.type.yaml | 23 + docs/models/demo/hello/linked.proto | 28 + docs/models/demo/library/favorites.yaml | 26 + docs/models/demo/library/library.proto | 48 ++ docs/models/demo/library/library.types.yaml | 41 + docs/models/enola/README.md | 17 + docs/models/enola/enola.md | 71 ++ docs/models/enola/enola.types.yaml | 91 ++ .../enola/schemas/Connector.schema.json | 113 +++ docs/models/enola/schemas/Data.schema.json | 74 ++ .../enola/schemas/EntityKind.schema.json | 524 +++++++++++ .../enola/schemas/EntityKinds.schema.json | 552 ++++++++++++ .../schemas/EntityRelationship.schema.json | 133 +++ .../schemas/FileSystemRepository.schema.json | 54 ++ docs/models/enola/schemas/Import.schema.json | 41 + docs/models/enola/schemas/Link.schema.json | 75 ++ .../models/enola/schemas/Property.schema.json | 74 ++ docs/models/enola/schemas/Type.schema.json | 783 +++++++++++++++++ docs/models/enola/schemas/Types.schema.json | 813 ++++++++++++++++++ docs/models/geographical/README.md | 25 + docs/models/github/github.types.yaml | 43 + docs/use/connector/model-fs.yaml | 4 +- docs/use/library/model.yaml | 2 +- docs/use/rosetta/index.md | 6 +- mkdocs.yaml | 5 +- test.bash | 3 + tools/protoc/protoc.bash | 41 + 50 files changed, 4602 insertions(+), 46 deletions(-) create mode 100644 .yamllint.yaml create mode 100644 core/impl/src/main/java/dev/enola/core/AbstractRepository.java create mode 100644 core/impl/src/main/java/dev/enola/core/type/TypeRepository.java create mode 100644 core/impl/src/test/java/dev/enola/core/meta/TypeRepositoryTest.java create mode 100644 core/impl/src/test/resources/test-types.yaml create mode 100644 core/lib/src/main/java/dev/enola/core/ByteSeq.java create mode 100644 core/lib/src/main/java/dev/enola/core/enola_ext.proto create mode 100644 core/lib/src/test/java/dev/enola/core/ByteSeqTest.java create mode 100644 docs/models/demo/hello/README.md create mode 100644 docs/models/demo/hello/greeting.proto create mode 100644 docs/models/demo/hello/greeting.type.yaml create mode 100644 docs/models/demo/hello/hello.type.yaml create mode 100644 docs/models/demo/hello/linked.proto create mode 100644 docs/models/demo/library/favorites.yaml create mode 100644 docs/models/demo/library/library.proto create mode 100644 docs/models/demo/library/library.types.yaml create mode 100644 docs/models/enola/README.md create mode 100644 docs/models/enola/enola.md create mode 100644 docs/models/enola/enola.types.yaml create mode 100644 docs/models/enola/schemas/Connector.schema.json create mode 100644 docs/models/enola/schemas/Data.schema.json create mode 100644 docs/models/enola/schemas/EntityKind.schema.json create mode 100644 docs/models/enola/schemas/EntityKinds.schema.json create mode 100644 docs/models/enola/schemas/EntityRelationship.schema.json create mode 100644 docs/models/enola/schemas/FileSystemRepository.schema.json create mode 100644 docs/models/enola/schemas/Import.schema.json create mode 100644 docs/models/enola/schemas/Link.schema.json create mode 100644 docs/models/enola/schemas/Property.schema.json create mode 100644 docs/models/enola/schemas/Type.schema.json create mode 100644 docs/models/enola/schemas/Types.schema.json create mode 100644 docs/models/geographical/README.md create mode 100644 docs/models/github/github.types.yaml create mode 100755 tools/protoc/protoc.bash diff --git a/.bazelrc b/.bazelrc index 8dc656b23..cc09eea03 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,6 +4,7 @@ common --enable_bzlmod common --extra_toolchains=@local_jdk//:all +# Java version must match .devcontainer/devcontainer.json # https://bazel.build/docs/bazel-and-java#java-versions build --java_language_version=21 build --tool_java_language_version=21 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f77050cd6..9a6d6aa38 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,16 +2,13 @@ { "name": "Enola.dev's Dev Containers.dev", + // TODO Define, build and use a Dev. Env. Dockerfile (see https://containers.dev/guide/dockerfile), + // to make it easy to use the required tools even outside of DevContainers in Codespaces! + // (And integrate onCreateCommand and features into that container, and documents its use.) + // This is the "fat" container with a lot of ready tools // from https://github.com/devcontainers/images/tree/main/src/universal "image": "mcr.microsoft.com/devcontainers/universal:2-linux", - // - // "image": "mcr.microsoft.com/devcontainers/base:bookworm" - // from https://github.com/devcontainers/images/tree/main/src/base-debian - // from https://github.com/devcontainers/templates/tree/main/src/debian - // is very minimal, and we would need to add a lot... - // - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // https://containers.dev/implementors/json_reference/#lifecycle-scripts // is what you want, NOT features' "ghcr.io/devcontainers-contrib/features/bash-command:1" @@ -20,8 +17,13 @@ // Features to add to the dev container. More info: https://containers.dev/features. "features": { + // Java version must match .bazelrc "ghcr.io/devcontainers/features/java:1": { "version": "21" + }, + // protoc is used by tools/protoc/protoc.bash + "ghcr.io/devcontainers-contrib/features/protoc-asdf:1": { + "version": "3.6.1" } }, diff --git a/.editorconfig b/.editorconfig index ecc3fcb4d..e4b478757 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,14 +43,17 @@ tab_width = unset [*.json] indent_size = 2 tab_width = 2 +max_line_length = unset [*.jsonc] indent_size = 2 tab_width = 2 +max_line_length = unset [*.json5] indent_size = 2 tab_width = 2 +max_line_length = unset [*.toml] indent_size = 2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aebdcdc7c..1e70bdc83 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -152,13 +152,44 @@ repos: hooks: - id: csslint - # https://editorconfig.org check should run AFTER all of the formatters (above) - - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: 2.7.3 + # https://yamllint.readthedocs.io/en/stable/integration.html#integration-with-pre-commit + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.33.0 hooks: - - id: editorconfig-checker + - id: yamllint + # https://yamllint.readthedocs.io/en/stable/configuration.html#errors-and-warnings + args: [--strict] - repo: https://github.com/shellcheck-py/shellcheck-py rev: v0.9.0.6 hooks: - id: shellcheck + + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.27.3 + hooks: + - id: check-github-actions + args: ["--verbose"] + - id: check-github-workflows + args: ["--verbose"] + - id: check-dependabot + args: ["--verbose"] + - id: check-renovate + args: ["--verbose"] + - id: check-metaschema + files: \.schema\.json$ + args: ["--verbose"] + # TODO Change once https://github.com/python-jsonschema/check-jsonschema/issues/340 is implemented + - id: check-jsonschema + # files: .+/models/.+\.(yaml|json)$ + files: \.types\.yaml$ + args: ["--verbose", "--schemafile", "docs/models/enola/schemas/Types.schema.json"] + - id: check-jsonschema + files: \.type\.yaml$ + args: ["--verbose", "--schemafile", "docs/models/enola/schemas/Type.schema.json"] + + # https://editorconfig.org check should run AFTER all of the formatters (above) + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: 2.7.3 + hooks: + - id: editorconfig-checker diff --git a/.vscode/settings.json b/.vscode/settings.json index 2af3c3509..976ec1146 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,13 +2,21 @@ "bazel.buildifierFixOnFormat": true, "bazel.enableCodeLens": true, "bazel.executable": "bazelisk", + "bazel.projectview.open": false, // https://github.com/bazelbuild/vscode-bazel/issues/216 // https://github.com/bazelbuild/vscode-bazel/issues/223 // https://github.com/bazelbuild/vscode-bazel/issues/261 - "bazel.projectview.open": false, "bazel.queriesShareServer": true, - "cSpell.words": ["Bazel", "bazelisk", "Classpath", "Enola", "Intelli", "protolint", "textproto"], + "cSpell.words": [ + "Bazel", + "bazelisk", + "Classpath", + "Enola", + "Intelli", + "protolint", + "textproto" + ], "editor.formatOnType": true, "editor.formatOnSave": true, @@ -28,6 +36,9 @@ "[markdown]": { "editor.defaultFormatter": "DavidAnson.vscode-markdownlint" }, + // TODO change to 'smartWithSelection' when it works better? + // https://code.visualstudio.com/updates/v1_86#_languages + "markdown.editor.pasteUrlAsFormattedLink.enabled": "never", "markdown.validate.fileLinks.enabled": "error", "markdown.validate.enabled": true, "markdown.validate.fragmentLinks.enabled": "error", @@ -41,6 +52,9 @@ "java.completion.importOrder": ["#", "", "javax", "java"], //# is static "java.format.settings.google.extra": "--aosp", // For 4 instead of 2 spaces! + // Keep this version in sync with the same version in .pre-commit-config.yaml + // NB: Changes to this are only taken into account on start-up, so need to restart. + "java.format.settings.google.version": "1.19.2", "[java]": { "editor.tabSize": 4, // Format Java using https://github.com/google/google-java-format, @@ -52,18 +66,22 @@ "source.addMissingImports": "never" } }, - // Keep this version in sync with the same version in .pre-commit-config.yaml - // NB: Changes to this are only taken into account on start-up, so need to restart. - "java.format.settings.google.version": "1.19.2", "java.compile.nullAnalysis.mode": "automatic", "java.configuration.updateBuildConfiguration": "automatic", "java.server.launchMode": "Standard", + "java": { + "completion.favoriteStaticMembers": ["com.google.common.truth.Truth.*"] + }, "[json5]": { "editor.tabSize": 2, "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[proto3]": { + "editor.defaultFormatter": "zxh404.vscode-proto3" + }, + // https://squidfunk.github.io/mkdocs-material/creating-your-site/#configuration "yaml.schemas": { "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yaml" @@ -87,22 +105,24 @@ "**/.factorypath": true, "**/.project": true, "**/.settings": true, - "**/*.crswap": true, "**/eclipse-bin/": true, "**/eclipse-testbin/": true, "**/.bazeltargets": true, + ".be": false, ".cache": false, + ".devcontainer": false, ".eclipse": false, ".git": false, + ".github": false, ".idea": false, ".ijwb": false, ".venv": false, + ".vscode": false, "bazel-bin": false, "bazel-enola": false, "bazel-out": false, "bazel-testlogs": false, - ".github": false, - ".vscode": false, + "bin": false, "cli": false, "common": false, "connectors": false, @@ -110,10 +130,8 @@ "docs": false, "site": false, "tools": false, - "web": false, "node_modules": false, - ".be": false, - "bin": false, - ".devcontainer": false + "web": false, + "BUILT": false } } diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 000000000..7996874f2 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2024 The Enola 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. + +# https://yamllint.readthedocs.io/en/stable/configuration.html + +rules: + # Let's just check this only via .editorconfig instead + line-length: disable diff --git a/core/impl/src/main/java/dev/enola/core/AbstractRepository.java b/core/impl/src/main/java/dev/enola/core/AbstractRepository.java new file mode 100644 index 000000000..86d82297e --- /dev/null +++ b/core/impl/src/main/java/dev/enola/core/AbstractRepository.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2024 The Enola 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; + +public abstract class AbstractRepository {} diff --git a/core/impl/src/main/java/dev/enola/core/EnolaServiceProvider.java b/core/impl/src/main/java/dev/enola/core/EnolaServiceProvider.java index 0e0634173..ddea271aa 100644 --- a/core/impl/src/main/java/dev/enola/core/EnolaServiceProvider.java +++ b/core/impl/src/main/java/dev/enola/core/EnolaServiceProvider.java @@ -30,6 +30,8 @@ import dev.enola.core.meta.EntityKindRepository; import dev.enola.core.meta.SchemaAspect; import dev.enola.core.meta.TypeRegistryWrapper; +import dev.enola.core.meta.TypeRegistryWrapper.Builder; +import dev.enola.core.type.TypeRepository; import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; @@ -41,8 +43,20 @@ public class EnolaServiceProvider { public EnolaServiceProvider(EntityKindRepository ekr) throws ValidationException, EnolaException { + this(ekr, TypeRepository.newBuilder().build()); + } + + public EnolaServiceProvider(EntityKindRepository ekr, TypeRepository tyr) + throws ValidationException, EnolaException { enolaService = new EnolaServiceRegistry(); var trb = TypeRegistryWrapper.newBuilder(); + process(ekr, trb); + process(tyr, trb); + this.typeRegistry = trb.build(); + } + + private void process(EntityKindRepository ekr, TypeRegistryWrapper.Builder trb) + throws ValidationException, EnolaException { for (var ek : ekr.list()) { var aspectsBuilder = ImmutableList.builder(); @@ -115,7 +129,10 @@ public EnolaServiceProvider(EntityKindRepository ekr) trb.add(aspect.getDescriptors()); } } - this.typeRegistry = trb.build(); + } + + private void process(TypeRepository tyr, Builder trb) { + // TODO Implement... } public EnolaService getEnolaService() { diff --git a/core/impl/src/main/java/dev/enola/core/type/TypeRepository.java b/core/impl/src/main/java/dev/enola/core/type/TypeRepository.java new file mode 100644 index 000000000..23ac9a476 --- /dev/null +++ b/core/impl/src/main/java/dev/enola/core/type/TypeRepository.java @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2024 The Enola 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.type; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.protobuf.TypeRegistry; + +import dev.enola.core.meta.proto.Type; + +/** + * Repository of {@link Type}. + * + *

Not to be confused with {@link TypeRegistry}. + */ +public class TypeRepository { + // TODO Extract what's general here to share with PropertyRepository et al. + + private final ImmutableSortedMap types; + + private TypeRepository(ImmutableSortedMap types) { + this.types = types; + } + + public Iterable list() { + return types.values(); + } + + public Iterable names() { + return types.keySet(); + } + + public Type getByName(String name) { + return types.get(name); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + private final ImmutableSortedMap.Builder types = + ImmutableSortedMap.naturalOrder(); + + public void add(Type.Builder type) { + require(type.getUri(), "uri"); + // TODO setUrl(...), based on some sort of baseURL to the Web UI + var name = require(type.getName(), "name"); + types.put(name, type.build()); + } + + public TypeRepository build() { + return new TypeRepository(types.build()); + } + + private T require(T what, String identification) { + if (what == null) + throw new IllegalArgumentException("Type '" + identification + "' is required"); + return what; + } + + private Builder() {} + } +} diff --git a/core/impl/src/test/java/dev/enola/core/meta/TypeRepositoryTest.java b/core/impl/src/test/java/dev/enola/core/meta/TypeRepositoryTest.java new file mode 100644 index 000000000..461c78025 --- /dev/null +++ b/core/impl/src/test/java/dev/enola/core/meta/TypeRepositoryTest.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2024 The Enola 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.meta; + +import dev.enola.common.io.resource.ClasspathResource; +import dev.enola.common.protobuf.ProtoIO; +import dev.enola.core.meta.proto.Types; + +import org.junit.Test; + +import java.io.IOException; + +public class TypeRepositoryTest { + + @Test + public void loadBaseYAML() throws IOException { + var types = Types.newBuilder(); + var resource = new ClasspathResource("test-types.yaml"); + new ProtoIO().read(resource, types, Types.class); + } +} diff --git a/core/impl/src/test/resources/test-types.yaml b/core/impl/src/test/resources/test-types.yaml new file mode 100644 index 000000000..327b73cb5 --- /dev/null +++ b/core/impl/src/test/resources/test-types.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# Switch from YAML to MD (with YAML in "header") + +types: + - name: enola.dev/test1 + emoji: πŸ”— + labels: + en: URL (Test) + string: + + - name: enola.dev/test2 + emoji: πŸ“§ + labels: + en: Email Address + de: Email Adresse + de-CH: E-Mail Adresse + string: + properties: + email: + mailto: + link: mailto:{email} + gmail-from: + # TODO Test encoding! The @ has to be %40 encoded... + link: https://mail.google.com/mail/u/0/#search/from%3A{email} + gmail-to: + link: https://mail.google.com/mail/u/0/#search/to%3A{email} diff --git a/core/lib/BUILD b/core/lib/BUILD index c868153a7..75144d384 100644 --- a/core/lib/BUILD +++ b/core/lib/BUILD @@ -35,10 +35,19 @@ load("@rules_proto_grpc//doc:defs.bzl", "doc_markdown_compile") # https://rules-proto-grpc.com/en/latest/example.html#step-3-write-a-build-file +proto_library( + name = "ext_proto", + srcs = ["src/main/java/dev/enola/core/enola_ext.proto"], + deps = [ + "@com_google_protobuf//:descriptor_proto", + ], +) + proto_library( name = "core_proto", srcs = ["src/main/java/dev/enola/core/enola_core.proto"], deps = [ + ":ext_proto", "@com_google_protobuf//:any_proto", "@com_google_protobuf//:descriptor_proto", "@com_google_protobuf//:struct_proto", @@ -58,7 +67,11 @@ proto_library( proto_library( name = "meta_proto", srcs = ["src/main/java/dev/enola/core/meta/enola_meta.proto"], - deps = [":core_proto"], + deps = [ + ":core_proto", + ":ext_proto", + "@com_google_protobuf//:empty_proto", + ], ) proto_library( @@ -92,6 +105,7 @@ java_proto_library( deps = [ "connector_proto", "core_proto", + "ext_proto", "meta_proto", "util_proto", ], @@ -135,6 +149,7 @@ doc_markdown_compile( protos = [ "connector_proto", "core_proto", + "ext_proto", "meta_proto", "util_proto", ], @@ -150,6 +165,7 @@ buf_proto_lint_test( protos = [ "connector_proto", "core_proto", + # "ext_proto", "meta_proto", "util_proto", ], @@ -197,11 +213,19 @@ java_library( "src/test/java/**/*Test.java", ])] +go_proto_library( + name = "ext_go_proto", + importpath = "dev/enola/core/ext", + protos = [":ext_proto"], + visibility = [], +) + go_proto_library( name = "core_go_proto", importpath = "dev/enola/core", protos = [":core_proto"], visibility = [], + deps = [":ext_go_proto"], ) go_proto_library( @@ -217,7 +241,10 @@ go_proto_library( importpath = "dev/enola/core/meta", protos = [":meta_proto"], visibility = [], - deps = [":core_go_proto"], + deps = [ + ":core_go_proto", + ":ext_go_proto", + ], ) go_proto_library( diff --git a/core/lib/src/main/java/dev/enola/core/ByteSeq.java b/core/lib/src/main/java/dev/enola/core/ByteSeq.java new file mode 100644 index 000000000..d410bd6c0 --- /dev/null +++ b/core/lib/src/main/java/dev/enola/core/ByteSeq.java @@ -0,0 +1,168 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2023-2024 The Enola 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 com.google.protobuf.ByteString; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.UUID; + +/** + * Sequence of Bytes, of variable length, and immutable; often used as an Enola Type / Entity ID. Do + * not use this for "BLOBs" (like images or so). The hashCode is cached. + * + *

In Enola, these IDs are intended to be unique, for a given (possibly clustered) instance. + * Federated Enola instances in theory could have duplicates, but with sufficiently long randomly + * generated bytes this is considered rare enough in practice that you can assume these are globally + * unique. + */ +// TODO Make this a (the first!) Enola "simple type", with verbs for create & asUUID! +// TODO Link to docs/**/enola.md once that's published on https://docs.enola.dev. +public final class ByteSeq implements Comparable { + + public static final ByteSeq EMPTY = new ByteSeq(new byte[0]); + + public static final class Builder { + private final byte[] bytes; + + private Builder(int size) { + bytes = new byte[size]; + } + + public ByteSeq build() { + return new ByteSeq(bytes); + } + + public Builder add(ByteBuffer bb) { + if (!bb.isReadOnly()) { + throw new IllegalArgumentException("ByteBuffer !isReadOnly()"); + } + bb.get(bytes); + return this; + } + } + + public static Builder builder(int size) { + return new Builder(size); + } + + // TODO public static final ByteSeq fromMultibase(String multibase) { + + /** + * Create a ByteSeq from an array of bytes. Prefer using the Builder instead of this, to avoid + * the implementation have to copy the array. + * + * @param bytes Bytes (which will be copied) + * @return the ByteSeq + */ + public static final ByteSeq from(byte[] bytes) { + if (bytes.length == 0) { + return EMPTY; + } + return new ByteSeq(Arrays.copyOf(bytes, bytes.length)); + } + + /** + * Create a ByteSeq from a Protocol Buffer "bytes" field. + * + * @param proto the ByteString + * @return the ByteSeq + */ + public static ByteSeq from(ByteString proto) { + return ByteSeq.builder(proto.size()).add(proto.asReadOnlyByteBuffer()).build(); + } + + // ? public static ByteSeq toByteSeq(Message proto) { return + // ByteSeq.copyFrom(proto.toByteArray()); } + + public static ByteSeq from(UUID uuid) { + var byteBuffer = ByteBuffer.allocate(16); + byteBuffer.putLong(uuid.getMostSignificantBits()); + byteBuffer.putLong(uuid.getLeastSignificantBits()); + byteBuffer.position(0); + + var builder = builder(16); + builder.add(byteBuffer.asReadOnlyBuffer()); + return builder.build(); + } + + private final byte[] bytes; + private transient int hashCode; + + private ByteSeq(byte[] bytes) { + this.bytes = bytes; + } + + public byte[] toBytes() { + return Arrays.copyOf(bytes, bytes.length); + } + + public int size() { + return bytes.length; + } + + public byte get(int index) { + return bytes[index]; + } + + // public String toString(Base base) { return Multibase.encode(Base.Base32, id.toByteArray()); } + + public ByteString toByteString() { + return ByteString.copyFrom(bytes); + } + + public UUID toUUID() { + if (bytes.length != 16) { + throw new IllegalStateException( + "toUUID() is currently only supported for length == 16, not: " + bytes.length); + } + ByteBuffer bb = ByteBuffer.wrap(bytes); + long high = bb.getLong(); + long low = bb.getLong(); + return new UUID(high, low); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Arrays.hashCode(bytes); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + var other = (ByteSeq) obj; + if (hashCode() != other.hashCode()) { + return false; + } + return Arrays.equals(bytes, other.bytes); + } + + @Override + public int compareTo(ByteSeq other) { + return Arrays.compare(bytes, other.bytes); + } +} diff --git a/core/lib/src/main/java/dev/enola/core/enola_core.proto b/core/lib/src/main/java/dev/enola/core/enola_core.proto index ec4257599..2ed7fd7d3 100644 --- a/core/lib/src/main/java/dev/enola/core/enola_core.proto +++ b/core/lib/src/main/java/dev/enola/core/enola_core.proto @@ -18,6 +18,7 @@ syntax = "proto3"; package dev.enola.core; +import "core/lib/src/main/java/dev/enola/core/enola_ext.proto"; import "google/protobuf/any.proto"; import "google/protobuf/descriptor.proto"; import "google/protobuf/timestamp.proto"; @@ -33,7 +34,6 @@ option go_package = "dev/enola/core"; // https://github.com/capnproto/capnproto/blob/master/c%2B%2B/src/capnp/schema.capnp, // et al. message Thing { - // TODO URI for Linked Data... oneof kind { // NB: There are intentionally no other "basic types" than string; because // this is ultimately only intended for end-user viewing (so we convert @@ -41,7 +41,6 @@ message Thing { LinkedText text = 4; List list = 5; Struct struct = 6; - // TODO Map? } message LinkedText { string string = 1; diff --git a/core/lib/src/main/java/dev/enola/core/enola_ext.proto b/core/lib/src/main/java/dev/enola/core/enola_ext.proto new file mode 100644 index 000000000..f4579e8d0 --- /dev/null +++ b/core/lib/src/main/java/dev/enola/core/enola_ext.proto @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 2023-2024 The Enola 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. + +syntax = "proto3"; + +package dev.enola; + +import "google/protobuf/descriptor.proto"; + +option java_string_check_utf8 = true; +option java_package = "dev.enola.core.ext.proto"; +option java_multiple_files = true; +option go_package = "dev/enola/core/ext"; + +extend google.protobuf.FieldOptions { + string type = 2734; +} + +// TODO Register 2734 on +// https://github.com/protocolbuffers/protobuf/blob/master/docs/options.md diff --git a/core/lib/src/main/java/dev/enola/core/meta/enola_meta.proto b/core/lib/src/main/java/dev/enola/core/meta/enola_meta.proto index f37063c9b..a77c43bd3 100644 --- a/core/lib/src/main/java/dev/enola/core/meta/enola_meta.proto +++ b/core/lib/src/main/java/dev/enola/core/meta/enola_meta.proto @@ -19,16 +19,153 @@ syntax = "proto3"; package dev.enola.core.meta; import "core/lib/src/main/java/dev/enola/core/enola_core.proto"; +import "core/lib/src/main/java/dev/enola/core/enola_ext.proto"; +import "google/protobuf/empty.proto"; option java_string_check_utf8 = true; option java_package = "dev.enola.core.meta.proto"; option java_multiple_files = true; option go_package = "dev/enola/core/meta"; -// 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; +message Import { + // URLs from where to load additional referenced Types. + repeated string types = 1 [(dev.enola.type) = "enola.dev/url"]; + + // TODO repeated string protos = 2; + // TODO repeated string json_schemas = 2; +} + +// Types are a collection of enola:dev.enola.core.meta.Type. +// +// This is kind of like a +// https://en.wikipedia.org/wiki/Domain_of_discourse#Universe_of_discourse +// or the https://en.wikipedia.org/wiki/Universe_(mathematics). +message Types { + // TODO Implement, with @Test! Import import = 1; + repeated Type types = 7; +} + +message Type { + // Unique ID of this Type. + // For example, "d19974d6-0695-458d-bdd4-3ad89578db92". + // + // This "provides a relatively short yet unambiguous way to refer to a type", + // as "fully-qualified type names may be large and waste space when + // transmitted on the wire", and it "lets programmers change the symbolic name + // while keeping a fixed ID" (inspired by + // https://capnproto.org/language.html#unique-ids). + // + // It's recommended that this is set by the human author of the Type, + // but if it's not, it will be automatically generated by hashing the name. + // (This defeats the purpose of a "permanent" ID - but it's at least possible + // to set it later, if a name is ever changed.) + // + // TODO "Nicely render" this in the Web UI, using dev.enola.core.ByteSeq. + // TODO Do we *really* this, actually? + // bytes id = 1 [(dev.enola.type) = "enola.dev/id"]; + + // Short technical name of this Type. + // Must be unique within the environment this Enola instance operates. + // Publicly, using something that looks like an IRI/URI/URL is a simple way + // for uniqueness. For example, "your.org/something" (which is technically a + // relative URI, by chance). This string is NOT (necessarily) a valid URL; + // e.g. you (generally) cannot "http GET your.org/something". As a convenience + // for humans which type this into their web browser, your.org MAY set up a + // "redirector" which responds with a 30x to somewhere "interesting" for a + // human (not a machine), but that's just "nice", nothing more. Enola will + // never use it as anything else than simply a unique string. + string name = 2 [(dev.enola.type) = "enola.dev/gun"]; + + // URL of this Type. + // For example, "https://demo.enola.dev/type/enola.dev/Person". + // You *CAN* http GET this URL. An Enola server will return a HTML page or + // JSON or something. This is NOT set by the author of the Type, but at + // runtime. It is based on the name. + // TODO string url = 3 [(dev.enola.type) = "enola.dev/url"]; + + // URI Template of instances of this Type. + // For example, "hello/{message}" - where the parameter "message" refers to a + // property; so instances would be e.g. "hello/world" and "hello/planets". + string uri = 4 [(dev.enola.type) = "enola.dev/uri-template"]; + + // Properties of this Type. + // TODO This needs to be Reference to a Property, not a contained Property... + map properties = 5; + + // Human readable label of this type, may be several words, any case. + // This is a map where the key is an IETF [BCP + // 47](https://www.rfc-editor.org/info/bcp47) "language code" (like "en" or + // "de-CH") and the value is text in that language. These can easily be + // changed at any time without breaking anything in Enola. + map labels = 20 [(dev.enola.type) = "enola.dev/mls"]; + + // Documentation description (as URL; either absolute, or URL relative to the + // model's location - from where a UI will serve it). + // TODO Is this a [(dev.enola.type) = "enola.dev/url"]? Really?? + string doc = 21 [(dev.enola.type) = "enola.dev/url"]; + + // The Emoji shown as prefix to the label in UIs, if there is no logo. + // This is not necessarily unique. + string emoji = 22; + + // Logo (as URL; either absolute, or URL relative to the model's location - + // from where a UI will serve it). + string logo_url = 23 [(dev.enola.type) = "enola.dev/url"]; + + // TODO Replace with... Type reference?!? Whoa. + oneof schema { + // EntityKind. + // This is from the original Enola design, and may later be removed. + // TODO Should this really be embedded? Really? Probably better an ID as + // reference? Good test! + // string entity_kind = 10 [(dev.enola.type) = "enola.dev/EntityKind"]; + EntityKind legacy = 30; + + // Protocol Buffers Message. + // For example, "google.protobuf.Timestamp". + // TODO We need to be able to "annotate" (?) Proto descriptors for LD... + string proto = 31 [(dev.enola.type) = "enola.dev/proto"]; + + // A "simple type" that "extends" a string. + // This is useful to add "semantics" to strings; e.g. URL, Email, etc. + google.protobuf.Empty string = 32; + + // A "binary" type, with "content" that's an (unstructured) bytes sequence. + google.protobuf.Empty binary = 33; + } + + string java = 50; + repeated string javas = 51; + + // TODO Verbs! +} + +message Property { + uint32 id = 1; + + // Type of Property, as Name of another Type. + // TODO Re-consider this... this is wrong, because Property is Types sharable. + // string type = 2 [(dev.enola.type) = "enola.dev/type"]; + + // Link to something related. + // Intended both for human consumption in a UI, as well as machine readable + // linked data relationships. Typically a Template of an (HTML a/href-like) + // HTTP URL, or enola: URI. Template parameters refer to other properties of + // the Type. + string link = 10 [(dev.enola.type) = "enola.dev/uri-template"]; + + // Human readable label of this property, may be several words, any case. + // This is a map where the key is an IETF [BCP + // 47](https://www.rfc-editor.org/info/bcp47) "language code" (like "en" or + // "de-CH") and the value is text in that language. These can easily be + // changed at any time without breaking anything in Enola. + map labels = 20 [(dev.enola.type) = "enola.dev/mls"]; + + // Documentation description (as URL; either absolute, or URL relative to the + // model's location - from where a UI will serve it). + // TODO Is this a [(dev.enola.type) = "enola.dev/url"]? Really?? + string doc = 21 [(dev.enola.type) = "enola.dev/url"]; +} message EntityKinds { repeated EntityKind kinds = 1; diff --git a/core/lib/src/test/java/dev/enola/core/ByteSeqTest.java b/core/lib/src/test/java/dev/enola/core/ByteSeqTest.java new file mode 100644 index 000000000..87dd7dfdc --- /dev/null +++ b/core/lib/src/test/java/dev/enola/core/ByteSeqTest.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2023-2024 The Enola 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 static com.google.common.truth.Truth.assertThat; + +import com.google.protobuf.ByteString; + +import org.junit.Test; + +import java.util.UUID; + +public class ByteSeqTest { + + @Test + public void byteArray() { + var array = new byte[] {1, 2, 3}; + var id = ByteSeq.from(array); + + assertThat(id.size()).isEqualTo(3); + assertThat(id.toBytes()).isEqualTo(array); + assertThat(id.get(1)).isEqualTo(2); + assertThat(id.hashCode()).isEqualTo(30817); + assertThat(id.equals(ByteSeq.from(array))).isTrue(); + + array[1] = 7; + assertThat(id.get(1)).isEqualTo(2); + } + + @Test + public void uuid() { + var uuid = UUID.randomUUID(); + var id = ByteSeq.from(uuid); + + assertThat(id.size()).isEqualTo(16); + assertThat(id.toUUID()).isEqualTo(uuid); + assertThat(id.toUUID().toString().length()).isEqualTo(36); + } + + @Test + public void protobufByteString() { + var byteString = ByteString.copyFromUtf8("hello, world πŸ˜ƒ"); + var id = ByteSeq.from(byteString); + + assertThat(id.size()).isEqualTo(17); + assertThat(id.hashCode()).isEqualTo(734434573); + assertThat(id.toByteString()).isEqualTo(byteString); + } + + @Test + public void compare() { + var id1 = ByteSeq.from(new byte[] {1}); + var id2 = ByteSeq.from(new byte[] {1, 2}); + + assertThat(id1.compareTo(id2)).isEqualTo(-1); + } + + @Test + public void empty() { + assertThat(ByteSeq.EMPTY.size()).isEqualTo(0); + assertThat(ByteSeq.EMPTY.toBytes()).isEqualTo(new byte[0]); + assertThat(ByteSeq.EMPTY.hashCode()).isEqualTo(1); + // TODOassertThat(ByteSeq.EMPTY.toUUID().toString()).isEqualTo("..."); + } +} diff --git a/docs/concepts/core.md b/docs/concepts/core.md index ff409b361..d6f470a0e 100644 --- a/docs/concepts/core.md +++ b/docs/concepts/core.md @@ -35,11 +35,15 @@ It _models_ real world concepts as what it terms _Entities,_ [identified by URI] across its Entities, as well as to arbitrary non-Enola URIs. (This notably includes traditional URLs like HTTP links, which models can use to create hyperlinks to UIs of applications managing Entities.) -Enola has [built-in interchangeable support](../use/rosetta/index.md) for JSON, YAML, and Text & Binary Protocol Buffers +## Formats + +Enola has [built-in interchangeable support](../use/rosetta/index.md) for JSON, YAML, and Text proto & Binary Protocol Buffers [wire formats](https://en.m.wikipedia.org/wiki/Comparison_of_data-serialization_formats) for entities. +## Schemas + Enola currently uses [Proto 3](https://protobuf.dev/programming-guides/proto3/) as its -Schema language, but is conceptually open to supporting other kinds of schemas in the future; perhaps e.g. -[JSON Schema](https://github.com/enola-dev/enola/issues/313), or XSD XML Schema, or +Schema language. It is conceptually open to supporting other kinds of schemas in the future; perhaps e.g. +[JSON Schema](https://github.com/enola-dev/enola/issues/313), or [Cap’n Proto](https://capnproto.org/language.html), or [TypeScript](https://www.typescriptlang.org/docs/handbook/2/objects.html) (Γ  la [Typson](https://github.com/lbovet/typson)), or [XML Schema](https://en.wikipedia.org/wiki/XML_Schema_(W3C)) (XSD), or [YANG](https://en.wikipedia.org/wiki/YANG) or [FHIR](https://www.hl7.org/fhir/) or [Varlink](https://varlink.org/Interface-Definition) or [Web IDL](https://webidl.spec.whatwg.org) or [ASN.1](https://en.m.wikipedia.org/wiki/ASN.1) or [GNU poke](https://www.gnu.org/software/poke/). diff --git a/docs/concepts/uri.md b/docs/concepts/uri.md index a4c0f9805..5f87f5df6 100644 --- a/docs/concepts/uri.md +++ b/docs/concepts/uri.md @@ -76,6 +76,6 @@ could include other protocols to connect to another Enola server, for federation ## Internationalization The project envisions to eventually fully support -[_Internationalized Resource Identifiers_](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier) -(URI), instead of ASCII only URI syntax; more testing to identify and fix any +_[Internationalized Resource Identifiers](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier)_ +(IRIs), instead of only ASCII URI syntax; more testing to identify and fix any related gaps is required. diff --git a/docs/dev/setup.md b/docs/dev/setup.md index a864e1d62..bb5de40bf 100644 --- a/docs/dev/setup.md +++ b/docs/dev/setup.md @@ -24,21 +24,24 @@ There are different Java (like Linux) "distributions" (all based on OpenJDK). The easiest way to install one of them is typically to use your OS' package manager: - sudo apt-get install openjdk-21-jdk openjdk-21-doc openjdk-21-source + sudo apt-get install openjdk-21-jdk openjdk-21-doc openjdk-21-source An alternative is to use e.g. [the SDKMAN!](https://sdkman.io) If you work on several projects using different Java versions, - then we recommend [using the great jEnv](https://www.jenv.be). + then we recommend using something like + [jEnv (with `.java-version`)](https://www.jenv.be), or + [asdf (with `.tool-versions`)](https://asdf-vm.com), or + [direnv (with `.envrc`)](https://direnv.net). 1. Install C/C++ etc. (it's required by the [Proto rule for Bazel](https://github.com/bazelbuild/rules_proto)), e.g. do: - sudo apt-get install build-essential + sudo apt-get install build-essential 1. Install [Python venv](https://docs.python.org/3/library/venv.html) (it's used by the presubmit and docs site generation), e.g. with: - sudo apt-get install python3-venv + sudo apt-get install python3-venv 1. Install [Bazelisk](https://github.com/bazelbuild/bazelisk) (NOT Bazel), on a (recent enough...) Debian/Ubuntu [with Go](https://go.dev/doc/install) diff --git a/docs/models/demo/hello/README.md b/docs/models/demo/hello/README.md new file mode 100644 index 000000000..472598aad --- /dev/null +++ b/docs/models/demo/hello/README.md @@ -0,0 +1,92 @@ + + +# Hello + +Please first [install Enola](../../../use/), if you haven't already. + +Then let's do a [`hello, world`](https://en.wikipedia.org/wiki/%22Hello,_World!%22_program)! + +## Type + +Greetings are a kind of _Type._ Let's define one, in [`hello.type.yaml`](hello.type.yaml): + + + +```yaml +... +``` + +Now let's use this: + +```bash +./enola --model hello.type.yaml get hello/world +world +``` + + + +## Schema + +Our first `enola.dev/demo/hello` _"type"_ was just a `string`. That was a great start - but now let's define our first _data structure._ We'll use [Protocol Buffers](https://protobuf.dev) (for now, [later others](../../../concepts/core.md#schemas)) to define a _"schema",_ in [`greeting.proto`](greeting.proto): + + + +```proto +... +``` + +and use this by changing that `schema: string:` from above to `proto: dev.enola.demo.hello.Greeting` as in [`greeting.type.yaml`](greeting.type.yaml), and then: + +```bash +./enola --model greeting.type.yaml get hello/world +message: world +``` + +The output is no longer just text, but now structured information - the `message` is `world`. This is YAML format. Enola also supports [other _formats_](../../../concepts/core.md#formats), e.g. as JSON: + +```bash +./enola --model greeting.type.yaml get --format=json hello/world +{ message: world } +``` + +## Template + +TODO Initially not for link but just set `message` to "hello, {{{ greeting }}}" prefix (change `uri: hello/{greeting}`) + +## Link + +TODO [`linked.proto`](linked.proto), with an `enola:` URI template, and here introduce the Web UI!!! + +https://docs.enola.dev/use/connector/#uri-templates + +## Connector + +https://docs.enola.dev/use/connector/#file-system-repository + +## Reflection + +```bash +./enola --model greeting.type.yaml get enola/type/greeting +name: enola.dev/demo/hello +uri: hello/{message} +schema: + proto: dev.enola.demo.hello.Greeting +``` + +Open in the Web interface, and note link to `Greeting`. diff --git a/docs/models/demo/hello/greeting.proto b/docs/models/demo/hello/greeting.proto new file mode 100644 index 000000000..58bf7198b --- /dev/null +++ b/docs/models/demo/hello/greeting.proto @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 2023-2024 The Enola 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. + +syntax = "proto3"; + +package dev.enola.demo.hello; + +message Greeting { + string message = 1; +} diff --git a/docs/models/demo/hello/greeting.type.yaml b/docs/models/demo/hello/greeting.type.yaml new file mode 100644 index 000000000..218515a70 --- /dev/null +++ b/docs/models/demo/hello/greeting.type.yaml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# TODO Publish schema & Remove yaml-language-server +# yaml-language-server: $schema=../../enola/schemas/Type.schema.json + +name: enola.dev/demo/hello +uri: hello/{message} +proto: greeting.proto#dev.enola.demo.hello.Greeting diff --git a/docs/models/demo/hello/hello.type.yaml b/docs/models/demo/hello/hello.type.yaml new file mode 100644 index 000000000..2f78964ee --- /dev/null +++ b/docs/models/demo/hello/hello.type.yaml @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# TODO Publish schema & Remove yaml-language-server +# yaml-language-server: $schema=../../enola/schemas/Type.schema.json + +name: enola.dev/demo/hello +uri: hello/{message} +# TODO Do we *really* need this? +# string: diff --git a/docs/models/demo/hello/linked.proto b/docs/models/demo/hello/linked.proto new file mode 100644 index 000000000..000a7a514 --- /dev/null +++ b/docs/models/demo/hello/linked.proto @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 2023-2024 The Enola 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. + +syntax = "proto3"; + +package dev.enola.demo.hello; + +import "core/lib/src/main/java/dev/enola/core/enola_ext.proto"; + +message Greeting { + string message = 1; + string google = 2 + [(dev.enola.url) = "https://www.google.com/search?q={{ message }}"]; + string link = 3 [(dev.enola.type) = "dev.enola.demo.hello"]; +} diff --git a/docs/models/demo/library/favorites.yaml b/docs/models/demo/library/favorites.yaml new file mode 100644 index 000000000..c7951bf4a --- /dev/null +++ b/docs/models/demo/library/favorites.yaml @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# TODO JSON Schema, generated from library.proto! + +isbn: 0434961604 +title: The Little Prince +rating: RATING_TOP_TIP + +--- +isbn: 0-13-140731-7 +rating: RATING_LIKE_IT +# title will be fetched by Connector! diff --git a/docs/models/demo/library/library.proto b/docs/models/demo/library/library.proto new file mode 100644 index 000000000..3c3d6b690 --- /dev/null +++ b/docs/models/demo/library/library.proto @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 2023-2024 The Enola 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. + +syntax = "proto3"; + +package dev.enola.demo.library; + +import "core/lib/src/main/java/dev/enola/core/enola_ext.proto"; + +message Book { + string isbn = 1; + // TODO Write a Connector using some REST API which fetches a Book's Title, + // given its ISBN. + string title = 2; + int32 pages = 3; +} + +message Favorite { + string isbn = 1 [(dev.enola.type) = "enola.dev/demo/library/book"]; + + Rating rating = 2; + + enum Rating { + RATING_UNSPECIFIED = 0; + RATING_OH_WELL = 1; + RATING_WHY_NOT = 2; + RATING_ITS_OKY = 3; + RATING_LIKE_IT = 4; + RATING_TOP_TIP = 5; + } +} + +message Favorites { + repeated Favorite favorites = 1; +} diff --git a/docs/models/demo/library/library.types.yaml b/docs/models/demo/library/library.types.yaml new file mode 100644 index 000000000..ee60da890 --- /dev/null +++ b/docs/models/demo/library/library.types.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# yaml-language-server: $schema=../../enola/schemas/Types.schema.json + +# TODO Demo how to alternative declare this in library.proto, with extensions. + +types: + - name: enola.dev/demo/library/book + proto: library.proto#dev.enola.demo.library.Book + emoji: πŸ“– + properties: + openlibrary: + link: https://openlibrary.org/search?isbn={path.isbn} + labels: + en: Open Library + google: + link: https://www.google.com/search?tbm=bks&q=isbn:{path.isbn} + labels: + en: Google Book Search + fr: Recherche de Livres sur Google + urn: + # https://en.m.wikipedia.org/wiki/Uniform_Resource_Name + link: urn:isbn:{isbn} + + - name: enola.dev/demo/library/favorites + proto: dev.enola.demo.library.Favorites + emoji: ⭐ diff --git a/docs/models/enola/README.md b/docs/models/enola/README.md new file mode 100644 index 000000000..9ccfbf949 --- /dev/null +++ b/docs/models/enola/README.md @@ -0,0 +1,17 @@ + diff --git a/docs/models/enola/enola.md b/docs/models/enola/enola.md new file mode 100644 index 000000000..6ffbdf8f7 --- /dev/null +++ b/docs/models/enola/enola.md @@ -0,0 +1,71 @@ + + +# Enola Built-In Types + + + +## URL + +An _URL_ is a _Uniform Resource Locator._ That sounds complicated, +but it's actually something everyone is very familiar with nowadays: + +It's simply that text which you type into your web browser's address bar, on top! +There is also (typically) one of these underlying what click on in a web page. + +For example, https://docs.enola.dev or https://www.google.com/search?q=enola.dev are both URLs. + +Most URLs start with `https:`, but there are also e.g. `file:` or `mailto:`, and some others; including [Enola's own `enola:`](https://docs.enola.dev/concepts/uri/). + +See also https://en.wikipedia.org/wiki/URL and https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL. + +## GUN + +A _GUN_ is a _Globally Unique Name._ This is an Enola.dev invented term, not an industry standard. + +It's simply a `string` that you are "fairly sure" is unique. For public Enola instances, an easy way to "guarantee" such uniqueness is the "convention" to start it with a domain name which you own, either in the format used in URLs (e.g. `enola.dev`) or the "reverse notation" (e.g. `dev.enola`) which is more popular in some environments (e.g. Java or Proto packages). + +Some GUNs may be URIs or even URLs, but this not specifically required. More technically, a GUN does NOT (necessarily) have a _scheme, path, authority, query, fragment_ structure. So "your.org/something" is a "valid GUN", as is really any string, but it's technically not a valid URL (because it has no scheme), but could be a valid (relative) URI - but that's a "coincidence". + +On private networks e.g. in internal corporate intranet deployments, a GUN can really be any string that you are comfortable with being unique within that Enola instance. + +## ID + +An _ID_ in Enola's context is simply a sequence of bytes. + +It is sometimes used internally as permanent technical identifier which may be shorter than e.g. a Type's GUN, or another entity name or path or such. + +The length is not fixed, but could be e.g. 16 bytes (128 bits) to represent an [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier); this should however never be relied upon. + +Binary Enola IDs can have various textual representations; for example, as UUID text (if it's 16 bytes, or less), or as a [Multiformats Multibase](https://multiformats.io), or any other such encoding. + +## MLS + +A _MLS_ in Enola is simply a string in multiple languages. + +Technically it's a `Map` where the keys are [IETF BCP 47](https://www.rfc-editor.org/info/bcp47) "language codes" (like e.g. `en` or `de-CH`) and the value is text in that language. + +## Email + +Email is what you know, an electronic message; here specifically an address you can send one to. + +## Proto + +In Enola's context, "proto" refers to https://protobuf.dev. + +Specifically, this type is a particular `message` structure. diff --git a/docs/models/enola/enola.types.yaml b/docs/models/enola/enola.types.yaml new file mode 100644 index 000000000..8a68fc67a --- /dev/null +++ b/docs/models/enola/enola.types.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# NB: schemas/Type.schema.json et al are generated by tools/protoc.bash! + +# TODO Move this into .vscode/settings.json +# (once https://docs.enola.dev/json-schema/enola_meta.schema.jsonc is published) +# yaml-language-server: $schema=schemas/Types.schema.json + +# TODO Support "flat" YAML with "multiple documents" using β€œ---” separators. This requires: +# +# 1. https://github.com/redhat-developer/vscode-yaml/issues/995, +# with https://github.com/redhat-developer/yaml-language-server/issues/946; +# +# 2. https://github.com/python-jsonschema/check-jsonschema/issues/222; +# +# 3. Enola reader support for it, for Rosetta et al; +# just make `ProtoIO.read()` return a List<> instead. + +# TODO Declare (or import?) the "fundamental" types here, first... +# Nothing, Unknown, Boolean, String, Text, etc. + +types: + - name: enola.dev/url + doc: enola.md#URL + emoji: πŸ”— + labels: + en: URL + string: + + - name: enola.dev/id + doc: enola.md#ID + emoji: πŸ†” + labels: + en: Enola (binary) ID + binary: + java: dev.enola.core.ByteSeq + + - name: enola.dev/gun + doc: enola.md#GUN + labels: + en: Globally Unique Name (GUN) + string: + + - name: enola.dev/email + doc: enola.md#Email + emoji: πŸ“§ + labels: + en: Email Address + de: Email Adresse + de-CH: E-Mail Adresse + string: + properties: + email: + mailto: + link: mailto:{email} + gmail-from: + # TODO Test encoding! The @ has to be %40 encoded... + link: https://mail.google.com/mail/u/0/#search/from%3A{email} + gmail-to: + link: https://mail.google.com/mail/u/0/#search/to%3A{email} + + - name: enola.dev/mls + doc: enola.md#MLS + emoji: 🌐 + labels: + en: Multi-language Strings + # TODO schema? It's a map .. but do we HAVE to specify a schema? For what?? + + - name: enola.dev/proto + doc: enola.md#Proto + emoji: πŸ—œοΈ + labels: + en: Protocol Buffer Message + string: + properties: + enola: + link: enola:enola.dev/proto/{fqn} diff --git a/docs/models/enola/schemas/Connector.schema.json b/docs/models/enola/schemas/Connector.schema.json new file mode 100644 index 000000000..4770311d4 --- /dev/null +++ b/docs/models/enola/schemas/Connector.schema.json @@ -0,0 +1,113 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Connector", + "definitions": { + "Connector": { + "properties": { + "error": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Always fails with this error message (for testing, only)." + }, + "java_class": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Java class name for in-process connector on the Java classpath." + }, + "fs": { + "$ref": "#/definitions/dev.enola.core.meta.FileSystemRepository", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "title": "TODO java_guice lookup?", + "description": "TODO java_guice lookup?" + }, + "grpc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Invokes remote connector via gRPC. The \"connection string\" here is a target endpoint in hostname:port format. (It's NOT an URI, so there is no scheme:// nor any /path/ or #fragment.)" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Connector" + }, + "dev.enola.core.meta.FileSystemRepository": { + "properties": { + "path": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "format": { + "enum": [ + "FORMAT_UNSPECIFIED", + 0, + "FORMAT_TEXTPROTO", + 1, + "FORMAT_YAML", + 2, + "FORMAT_JSON", + 3 + ], + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Format" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "File System Repository" + } + } +} diff --git a/docs/models/enola/schemas/Data.schema.json b/docs/models/enola/schemas/Data.schema.json new file mode 100644 index 000000000..4d698cfc4 --- /dev/null +++ b/docs/models/enola/schemas/Data.schema.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Data", + "definitions": { + "Data": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + }, + "type_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The fully qualified name of the (root) Protocol Buffer Message; see https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/any.proto" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Data" + } + } +} diff --git a/docs/models/enola/schemas/EntityKind.schema.json b/docs/models/enola/schemas/EntityKind.schema.json new file mode 100644 index 000000000..89da7db54 --- /dev/null +++ b/docs/models/enola/schemas/EntityKind.schema.json @@ -0,0 +1,524 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/EntityKind", + "definitions": { + "EntityKind": { + "properties": { + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID. The ns may be filled in by the reader, if omitted in *-model.textproto. The entity is the name of THIS EntityKind! This is typically never changed anymore after initial creation. The path contains' the segments' names (here, whereas in Entity it's the \"values\")." + }, + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label of entity, may be several words, any case. This can easily be changed at any time." + }, + "emoji": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The Emoji shown as prefix to the name in UIs, if there is no logo." + }, + "logo_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Logo (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "doc_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "related": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.EntityRelationship", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of related Entities. The string keys here match Entity#related's." + }, + "link": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Link", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of URL links. The string keys here match Entity#links's." + }, + "data": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Data", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of data about the Entity, in machine readable form. The string keys here match Entity#data's." + }, + "connectors": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "items": { + "$ref": "#/definitions/dev.enola.core.meta.Connector" + }, + "type": "array" + } + ] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Kind", + "description": "Kind of an Entity, as in message Entity." + }, + "dev.enola.core.ID": { + "properties": { + "ns": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Namespace. Serves to distinguish same entity names (below). This is optional if in your use of Enola you avoid name conflicts. It's like in C# or \"package\" in Java or Go or the xmlns: from XML Schema, or whatever the hell confusing thing ;) that Python is doing about this. Validated to only contain [a-z0-9_.] characters, so it's safe in URLs. By convention can contain '.' for sub-namespacing, but does not have to. The namespace of an Entity is always the same as its EntityKind." + }, + "entity": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Entity Kind Name. This is mandatory and thus always present. This refers to an EntityKind and not an individual Entity, despite the name. (In practice this is just shorter and clearer for people to understand.) Validated to only contain [a-z0-9_] characters, so it's safe in URLs." + }, + "paths": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ], + "description": "Path. This is mandatory and thus always present with at least 1 element. Think of this as what would typically uniquely identify this entity IRL; e.g. a \"hostname\" or some UUID or a S/N or whatever is its \"primary key\". Validated to only contain [a-z0-9_-.] characters, so it's safe in URLs. (This restriction could in theory be relaxed, if there was a strong need to support it; as long as sufficient test coverage is added for correct encoding in URIs, see https://en.m.wikipedia.org/wiki/URL_encoding.) Multiple \"segments\" are supported for \"composed keys\", for example a network/context/namespace/name kind of ID." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Id", + "description": "TODO Replace this with //docs/concepts/uri.md! ID of an Entity known to Enola, fully qualified. Can be formatted to and parsed from several different string text forms, see Java class dev.enola.core.IDs.java" + }, + "dev.enola.core.meta.Connector": { + "properties": { + "error": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Always fails with this error message (for testing, only)." + }, + "java_class": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Java class name for in-process connector on the Java classpath." + }, + "fs": { + "$ref": "#/definitions/dev.enola.core.meta.FileSystemRepository", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "title": "TODO java_guice lookup?", + "description": "TODO java_guice lookup?" + }, + "grpc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Invokes remote connector via gRPC. The \"connection string\" here is a target endpoint in hostname:port format. (It's NOT an URI, so there is no scheme:// nor any /path/ or #fragment.)" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Connector" + }, + "dev.enola.core.meta.Data": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + }, + "type_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The fully qualified name of the (root) Protocol Buffer Message; see https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/any.proto" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Data" + }, + "dev.enola.core.meta.EntityRelationship": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID reference to another Entity. This ID's ns/entity/paths fields can contain a template, like Link#uri_template. Alternatively, this can be left empty, and set by connectors." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Relationship", + "description": "Entity#related map model; its key is the same as this in EntityKind#related." + }, + "dev.enola.core.meta.FileSystemRepository": { + "properties": { + "path": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "format": { + "enum": [ + "FORMAT_UNSPECIFIED", + 0, + "FORMAT_TEXTPROTO", + 1, + "FORMAT_YAML", + 2, + "FORMAT_JSON", + 3 + ], + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Format" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "File System Repository" + }, + "dev.enola.core.meta.Link": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "uri_template": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "URI template, to create URL. As an ID URI Template (RFC 6570); see https://en.wikipedia.org/wiki/URI_Template. The available variables are the ID's path parameters, as well as a special proto.* which allows to declaratively create links out of the Any proto (instead of coding link generation in the service; which is always still also possible)." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Link", + "description": "Entity#link map model; its key is the same as this in EntityKind#link." + } + } +} diff --git a/docs/models/enola/schemas/EntityKinds.schema.json b/docs/models/enola/schemas/EntityKinds.schema.json new file mode 100644 index 000000000..de58213d0 --- /dev/null +++ b/docs/models/enola/schemas/EntityKinds.schema.json @@ -0,0 +1,552 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/EntityKinds", + "definitions": { + "EntityKinds": { + "properties": { + "kinds": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "items": { + "$ref": "#/definitions/dev.enola.core.meta.EntityKind" + }, + "type": "array" + } + ] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Kinds" + }, + "dev.enola.core.ID": { + "properties": { + "ns": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Namespace. Serves to distinguish same entity names (below). This is optional if in your use of Enola you avoid name conflicts. It's like in C# or \"package\" in Java or Go or the xmlns: from XML Schema, or whatever the hell confusing thing ;) that Python is doing about this. Validated to only contain [a-z0-9_.] characters, so it's safe in URLs. By convention can contain '.' for sub-namespacing, but does not have to. The namespace of an Entity is always the same as its EntityKind." + }, + "entity": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Entity Kind Name. This is mandatory and thus always present. This refers to an EntityKind and not an individual Entity, despite the name. (In practice this is just shorter and clearer for people to understand.) Validated to only contain [a-z0-9_] characters, so it's safe in URLs." + }, + "paths": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ], + "description": "Path. This is mandatory and thus always present with at least 1 element. Think of this as what would typically uniquely identify this entity IRL; e.g. a \"hostname\" or some UUID or a S/N or whatever is its \"primary key\". Validated to only contain [a-z0-9_-.] characters, so it's safe in URLs. (This restriction could in theory be relaxed, if there was a strong need to support it; as long as sufficient test coverage is added for correct encoding in URIs, see https://en.m.wikipedia.org/wiki/URL_encoding.) Multiple \"segments\" are supported for \"composed keys\", for example a network/context/namespace/name kind of ID." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Id", + "description": "TODO Replace this with //docs/concepts/uri.md! ID of an Entity known to Enola, fully qualified. Can be formatted to and parsed from several different string text forms, see Java class dev.enola.core.IDs.java" + }, + "dev.enola.core.meta.Connector": { + "properties": { + "error": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Always fails with this error message (for testing, only)." + }, + "java_class": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Java class name for in-process connector on the Java classpath." + }, + "fs": { + "$ref": "#/definitions/dev.enola.core.meta.FileSystemRepository", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "title": "TODO java_guice lookup?", + "description": "TODO java_guice lookup?" + }, + "grpc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Invokes remote connector via gRPC. The \"connection string\" here is a target endpoint in hostname:port format. (It's NOT an URI, so there is no scheme:// nor any /path/ or #fragment.)" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Connector" + }, + "dev.enola.core.meta.Data": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + }, + "type_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The fully qualified name of the (root) Protocol Buffer Message; see https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/any.proto" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Data" + }, + "dev.enola.core.meta.EntityKind": { + "properties": { + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID. The ns may be filled in by the reader, if omitted in *-model.textproto. The entity is the name of THIS EntityKind! This is typically never changed anymore after initial creation. The path contains' the segments' names (here, whereas in Entity it's the \"values\")." + }, + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label of entity, may be several words, any case. This can easily be changed at any time." + }, + "emoji": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The Emoji shown as prefix to the name in UIs, if there is no logo." + }, + "logo_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Logo (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "doc_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "related": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.EntityRelationship", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of related Entities. The string keys here match Entity#related's." + }, + "link": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Link", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of URL links. The string keys here match Entity#links's." + }, + "data": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Data", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of data about the Entity, in machine readable form. The string keys here match Entity#data's." + }, + "connectors": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "items": { + "$ref": "#/definitions/dev.enola.core.meta.Connector" + }, + "type": "array" + } + ] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Kind", + "description": "Kind of an Entity, as in message Entity." + }, + "dev.enola.core.meta.EntityRelationship": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID reference to another Entity. This ID's ns/entity/paths fields can contain a template, like Link#uri_template. Alternatively, this can be left empty, and set by connectors." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Relationship", + "description": "Entity#related map model; its key is the same as this in EntityKind#related." + }, + "dev.enola.core.meta.FileSystemRepository": { + "properties": { + "path": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "format": { + "enum": [ + "FORMAT_UNSPECIFIED", + 0, + "FORMAT_TEXTPROTO", + 1, + "FORMAT_YAML", + 2, + "FORMAT_JSON", + 3 + ], + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Format" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "File System Repository" + }, + "dev.enola.core.meta.Link": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "uri_template": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "URI template, to create URL. As an ID URI Template (RFC 6570); see https://en.wikipedia.org/wiki/URI_Template. The available variables are the ID's path parameters, as well as a special proto.* which allows to declaratively create links out of the Any proto (instead of coding link generation in the service; which is always still also possible)." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Link", + "description": "Entity#link map model; its key is the same as this in EntityKind#link." + } + } +} diff --git a/docs/models/enola/schemas/EntityRelationship.schema.json b/docs/models/enola/schemas/EntityRelationship.schema.json new file mode 100644 index 000000000..b83e2318e --- /dev/null +++ b/docs/models/enola/schemas/EntityRelationship.schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/EntityRelationship", + "definitions": { + "EntityRelationship": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID reference to another Entity. This ID's ns/entity/paths fields can contain a template, like Link#uri_template. Alternatively, this can be left empty, and set by connectors." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Relationship", + "description": "Entity#related map model; its key is the same as this in EntityKind#related." + }, + "dev.enola.core.ID": { + "properties": { + "ns": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Namespace. Serves to distinguish same entity names (below). This is optional if in your use of Enola you avoid name conflicts. It's like in C# or \"package\" in Java or Go or the xmlns: from XML Schema, or whatever the hell confusing thing ;) that Python is doing about this. Validated to only contain [a-z0-9_.] characters, so it's safe in URLs. By convention can contain '.' for sub-namespacing, but does not have to. The namespace of an Entity is always the same as its EntityKind." + }, + "entity": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Entity Kind Name. This is mandatory and thus always present. This refers to an EntityKind and not an individual Entity, despite the name. (In practice this is just shorter and clearer for people to understand.) Validated to only contain [a-z0-9_] characters, so it's safe in URLs." + }, + "paths": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ], + "description": "Path. This is mandatory and thus always present with at least 1 element. Think of this as what would typically uniquely identify this entity IRL; e.g. a \"hostname\" or some UUID or a S/N or whatever is its \"primary key\". Validated to only contain [a-z0-9_-.] characters, so it's safe in URLs. (This restriction could in theory be relaxed, if there was a strong need to support it; as long as sufficient test coverage is added for correct encoding in URIs, see https://en.m.wikipedia.org/wiki/URL_encoding.) Multiple \"segments\" are supported for \"composed keys\", for example a network/context/namespace/name kind of ID." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Id", + "description": "TODO Replace this with //docs/concepts/uri.md! ID of an Entity known to Enola, fully qualified. Can be formatted to and parsed from several different string text forms, see Java class dev.enola.core.IDs.java" + } + } +} diff --git a/docs/models/enola/schemas/FileSystemRepository.schema.json b/docs/models/enola/schemas/FileSystemRepository.schema.json new file mode 100644 index 000000000..cda9d3618 --- /dev/null +++ b/docs/models/enola/schemas/FileSystemRepository.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/FileSystemRepository", + "definitions": { + "FileSystemRepository": { + "properties": { + "path": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "format": { + "enum": [ + "FORMAT_UNSPECIFIED", + 0, + "FORMAT_TEXTPROTO", + 1, + "FORMAT_YAML", + 2, + "FORMAT_JSON", + 3 + ], + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Format" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "File System Repository" + } + } +} diff --git a/docs/models/enola/schemas/Import.schema.json b/docs/models/enola/schemas/Import.schema.json new file mode 100644 index 000000000..d05bdd76f --- /dev/null +++ b/docs/models/enola/schemas/Import.schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Import", + "definitions": { + "Import": { + "properties": { + "types": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ], + "description": "URLs from where to load additional referenced Types." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Import" + } + } +} diff --git a/docs/models/enola/schemas/Link.schema.json b/docs/models/enola/schemas/Link.schema.json new file mode 100644 index 000000000..f154ca80c --- /dev/null +++ b/docs/models/enola/schemas/Link.schema.json @@ -0,0 +1,75 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Link", + "definitions": { + "Link": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "uri_template": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "URI template, to create URL. As an ID URI Template (RFC 6570); see https://en.wikipedia.org/wiki/URI_Template. The available variables are the ID's path parameters, as well as a special proto.* which allows to declaratively create links out of the Any proto (instead of coding link generation in the service; which is always still also possible)." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Link", + "description": "Entity#link map model; its key is the same as this in EntityKind#link." + } + } +} diff --git a/docs/models/enola/schemas/Property.schema.json b/docs/models/enola/schemas/Property.schema.json new file mode 100644 index 000000000..dc1ae540f --- /dev/null +++ b/docs/models/enola/schemas/Property.schema.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Property", + "definitions": { + "Property": { + "properties": { + "id": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "integer" + } + ] + }, + "link": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "title": "Type of Property, as Name of another Type.\n TODO Re-consider this... this is wrong, because Property is Types sharable.\n string type = 2 [(dev.enola.type) = \"enola.dev/type\"];", + "description": "Type of Property, as Name of another Type. TODO Re-consider this... this is wrong, because Property is Types sharable. string type = 2 [(dev.enola.type) = \"enola.dev/type\"]; Link to something related. Intended both for human consumption in a UI, as well as machine readable linked data relationships. Typically a Template of an (HTML a/href-like) HTTP URL, or enola: URI. Template parameters refer to other properties of the Type." + }, + "labels": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Human readable label of this property, may be several words, any case. This is a map where the key is an IETF [BCP 47](https://www.rfc-editor.org/info/bcp47) \"language code\" (like \"en\" or \"de-CH\") and the value is text in that language. These can easily be changed at any time without breaking anything in Enola." + }, + "doc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's location - from where a UI will serve it). TODO Is this a [(dev.enola.type) = \"enola.dev/url\"]? Really??" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Property" + } + } +} diff --git a/docs/models/enola/schemas/Type.schema.json b/docs/models/enola/schemas/Type.schema.json new file mode 100644 index 000000000..208c50b8b --- /dev/null +++ b/docs/models/enola/schemas/Type.schema.json @@ -0,0 +1,783 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Type", + "definitions": { + "Type": { + "properties": { + "name": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short technical name of this Type. Must be unique within the environment this Enola instance operates. Publicly, using something that looks like an IRI/URI/URL is a simple way for uniqueness. For example, \"your.org/something\" (which is technically a relative URI, by chance). This string is NOT (necessarily) a valid URL; e.g. you (generally) cannot \"http GET your.org/something\". As a convenience for humans which type this into their web browser, your.org MAY set up a \"redirector\" which responds with a 30x to somewhere \"interesting\" for a human (not a machine), but that's just \"nice\", nothing more. Enola will never use it as anything else than simply a unique string." + }, + "uri": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "title": "URL of this Type.\n For example, \"https://demo.enola.dev/type/enola.dev/Person\".\n You *CAN* http GET this URL. An Enola server will return a HTML page or\n JSON or something. This is NOT set by the author of the Type, but at\n runtime. It is based on the name.\n TODO string url = 3 [(dev.enola.type) = \"enola.dev/url\"];", + "description": "URL of this Type. For example, \"https://demo.enola.dev/type/enola.dev/Person\". You *CAN* http GET this URL. An Enola server will return a HTML page or JSON or something. This is NOT set by the author of the Type, but at runtime. It is based on the name. TODO string url = 3 [(dev.enola.type) = \"enola.dev/url\"]; URI Template of instances of this Type. For example, \"hello/{message}\" - where the parameter \"message\" refers to a property; so instances would be e.g. \"hello/world\" and \"hello/planets\"." + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Property", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Properties of this Type. TODO This needs to be Reference to a Property, not a contained Property..." + }, + "labels": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Human readable label of this type, may be several words, any case. This is a map where the key is an IETF [BCP 47](https://www.rfc-editor.org/info/bcp47) \"language code\" (like \"en\" or \"de-CH\") and the value is text in that language. These can easily be changed at any time without breaking anything in Enola." + }, + "doc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's location - from where a UI will serve it). TODO Is this a [(dev.enola.type) = \"enola.dev/url\"]? Really??" + }, + "emoji": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The Emoji shown as prefix to the label in UIs, if there is no logo. This is not necessarily unique." + }, + "logo_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Logo (as URL; either absolute, or URL relative to the model's location - from where a UI will serve it)." + }, + "legacy": { + "$ref": "#/definitions/dev.enola.core.meta.EntityKind", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "EntityKind. This is from the original Enola design, and may later be removed. TODO Should this really be embedded? Really? Probably better an ID as reference? Good test! string entity_kind = 10 [(dev.enola.type) = \"enola.dev/EntityKind\"];" + }, + "proto": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Protocol Buffers Message. For example, \"google.protobuf.Timestamp\". TODO We need to be able to \"annotate\" (?) Proto descriptors for LD..." + }, + "string": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Empty", + "description": "A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. A typical example is to use it as the request or the response type of an API method. For instance: service Foo { rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); }" + }, + "binary": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Empty", + "description": "A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. A typical example is to use it as the request or the response type of an API method. For instance: service Foo { rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); }" + }, + "java": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "javas": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Type", + "description": "Unique ID of this Type. For example, \"d19974d6-0695-458d-bdd4-3ad89578db92\". This \"provides a relatively short yet unambiguous way to refer to a type\", as \"fully-qualified type names may be large and waste space when transmitted on the wire\", and it \"lets programmers change the symbolic name while keeping a fixed ID\" (inspired by https://capnproto.org/language.html#unique-ids). It's recommended that this is set by the human author of the Type, but if it's not, it will be automatically generated by hashing the name. (This defeats the purpose of a \"permanent\" ID - but it's at least possible to set it later, if a name is ever changed.) TODO \"Nicely render\" this in the Web UI, using dev.enola.core.ByteSeq. TODO Do we *really* this, actually? bytes id = 1 [(dev.enola.type) = \"enola.dev/id\"];" + }, + "dev.enola.core.ID": { + "properties": { + "ns": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Namespace. Serves to distinguish same entity names (below). This is optional if in your use of Enola you avoid name conflicts. It's like in C# or \"package\" in Java or Go or the xmlns: from XML Schema, or whatever the hell confusing thing ;) that Python is doing about this. Validated to only contain [a-z0-9_.] characters, so it's safe in URLs. By convention can contain '.' for sub-namespacing, but does not have to. The namespace of an Entity is always the same as its EntityKind." + }, + "entity": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Entity Kind Name. This is mandatory and thus always present. This refers to an EntityKind and not an individual Entity, despite the name. (In practice this is just shorter and clearer for people to understand.) Validated to only contain [a-z0-9_] characters, so it's safe in URLs." + }, + "paths": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ], + "description": "Path. This is mandatory and thus always present with at least 1 element. Think of this as what would typically uniquely identify this entity IRL; e.g. a \"hostname\" or some UUID or a S/N or whatever is its \"primary key\". Validated to only contain [a-z0-9_-.] characters, so it's safe in URLs. (This restriction could in theory be relaxed, if there was a strong need to support it; as long as sufficient test coverage is added for correct encoding in URIs, see https://en.m.wikipedia.org/wiki/URL_encoding.) Multiple \"segments\" are supported for \"composed keys\", for example a network/context/namespace/name kind of ID." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Id", + "description": "TODO Replace this with //docs/concepts/uri.md! ID of an Entity known to Enola, fully qualified. Can be formatted to and parsed from several different string text forms, see Java class dev.enola.core.IDs.java" + }, + "dev.enola.core.meta.Connector": { + "properties": { + "error": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Always fails with this error message (for testing, only)." + }, + "java_class": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Java class name for in-process connector on the Java classpath." + }, + "fs": { + "$ref": "#/definitions/dev.enola.core.meta.FileSystemRepository", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "title": "TODO java_guice lookup?", + "description": "TODO java_guice lookup?" + }, + "grpc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Invokes remote connector via gRPC. The \"connection string\" here is a target endpoint in hostname:port format. (It's NOT an URI, so there is no scheme:// nor any /path/ or #fragment.)" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Connector" + }, + "dev.enola.core.meta.Data": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + }, + "type_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The fully qualified name of the (root) Protocol Buffer Message; see https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/any.proto" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Data" + }, + "dev.enola.core.meta.EntityKind": { + "properties": { + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID. The ns may be filled in by the reader, if omitted in *-model.textproto. The entity is the name of THIS EntityKind! This is typically never changed anymore after initial creation. The path contains' the segments' names (here, whereas in Entity it's the \"values\")." + }, + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label of entity, may be several words, any case. This can easily be changed at any time." + }, + "emoji": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The Emoji shown as prefix to the name in UIs, if there is no logo." + }, + "logo_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Logo (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "doc_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "related": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.EntityRelationship", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of related Entities. The string keys here match Entity#related's." + }, + "link": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Link", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of URL links. The string keys here match Entity#links's." + }, + "data": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Data", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of data about the Entity, in machine readable form. The string keys here match Entity#data's." + }, + "connectors": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "items": { + "$ref": "#/definitions/dev.enola.core.meta.Connector" + }, + "type": "array" + } + ] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Kind", + "description": "Kind of an Entity, as in message Entity." + }, + "dev.enola.core.meta.EntityRelationship": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID reference to another Entity. This ID's ns/entity/paths fields can contain a template, like Link#uri_template. Alternatively, this can be left empty, and set by connectors." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Relationship", + "description": "Entity#related map model; its key is the same as this in EntityKind#related." + }, + "dev.enola.core.meta.FileSystemRepository": { + "properties": { + "path": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "format": { + "enum": [ + "FORMAT_UNSPECIFIED", + 0, + "FORMAT_TEXTPROTO", + 1, + "FORMAT_YAML", + 2, + "FORMAT_JSON", + 3 + ], + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Format" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "File System Repository" + }, + "dev.enola.core.meta.Link": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "uri_template": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "URI template, to create URL. As an ID URI Template (RFC 6570); see https://en.wikipedia.org/wiki/URI_Template. The available variables are the ID's path parameters, as well as a special proto.* which allows to declaratively create links out of the Any proto (instead of coding link generation in the service; which is always still also possible)." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Link", + "description": "Entity#link map model; its key is the same as this in EntityKind#link." + }, + "dev.enola.core.meta.Property": { + "properties": { + "id": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "integer" + } + ] + }, + "link": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "title": "Type of Property, as Name of another Type.\n TODO Re-consider this... this is wrong, because Property is Types sharable.\n string type = 2 [(dev.enola.type) = \"enola.dev/type\"];", + "description": "Type of Property, as Name of another Type. TODO Re-consider this... this is wrong, because Property is Types sharable. string type = 2 [(dev.enola.type) = \"enola.dev/type\"]; Link to something related. Intended both for human consumption in a UI, as well as machine readable linked data relationships. Typically a Template of an (HTML a/href-like) HTTP URL, or enola: URI. Template parameters refer to other properties of the Type." + }, + "labels": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Human readable label of this property, may be several words, any case. This is a map where the key is an IETF [BCP 47](https://www.rfc-editor.org/info/bcp47) \"language code\" (like \"en\" or \"de-CH\") and the value is text in that language. These can easily be changed at any time without breaking anything in Enola." + }, + "doc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's location - from where a UI will serve it). TODO Is this a [(dev.enola.type) = \"enola.dev/url\"]? Really??" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Property" + } + } +} diff --git a/docs/models/enola/schemas/Types.schema.json b/docs/models/enola/schemas/Types.schema.json new file mode 100644 index 000000000..e0a139877 --- /dev/null +++ b/docs/models/enola/schemas/Types.schema.json @@ -0,0 +1,813 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Types", + "definitions": { + "Types": { + "properties": { + "types": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "items": { + "$ref": "#/definitions/dev.enola.core.meta.Type" + }, + "type": "array" + } + ], + "description": "TODO Implement, with @Test! Import import = 1;" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Types", + "description": "Types are a collection of enola:dev.enola.core.meta.Type. This is kind of like a https://en.wikipedia.org/wiki/Domain_of_discourse#Universe_of_discourse or the https://en.wikipedia.org/wiki/Universe_(mathematics)." + }, + "dev.enola.core.ID": { + "properties": { + "ns": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Namespace. Serves to distinguish same entity names (below). This is optional if in your use of Enola you avoid name conflicts. It's like in C# or \"package\" in Java or Go or the xmlns: from XML Schema, or whatever the hell confusing thing ;) that Python is doing about this. Validated to only contain [a-z0-9_.] characters, so it's safe in URLs. By convention can contain '.' for sub-namespacing, but does not have to. The namespace of an Entity is always the same as its EntityKind." + }, + "entity": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Entity Kind Name. This is mandatory and thus always present. This refers to an EntityKind and not an individual Entity, despite the name. (In practice this is just shorter and clearer for people to understand.) Validated to only contain [a-z0-9_] characters, so it's safe in URLs." + }, + "paths": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ], + "description": "Path. This is mandatory and thus always present with at least 1 element. Think of this as what would typically uniquely identify this entity IRL; e.g. a \"hostname\" or some UUID or a S/N or whatever is its \"primary key\". Validated to only contain [a-z0-9_-.] characters, so it's safe in URLs. (This restriction could in theory be relaxed, if there was a strong need to support it; as long as sufficient test coverage is added for correct encoding in URIs, see https://en.m.wikipedia.org/wiki/URL_encoding.) Multiple \"segments\" are supported for \"composed keys\", for example a network/context/namespace/name kind of ID." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Id", + "description": "TODO Replace this with //docs/concepts/uri.md! ID of an Entity known to Enola, fully qualified. Can be formatted to and parsed from several different string text forms, see Java class dev.enola.core.IDs.java" + }, + "dev.enola.core.meta.Connector": { + "properties": { + "error": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Always fails with this error message (for testing, only)." + }, + "java_class": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Java class name for in-process connector on the Java classpath." + }, + "fs": { + "$ref": "#/definitions/dev.enola.core.meta.FileSystemRepository", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "title": "TODO java_guice lookup?", + "description": "TODO java_guice lookup?" + }, + "grpc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Invokes remote connector via gRPC. The \"connection string\" here is a target endpoint in hostname:port format. (It's NOT an URI, so there is no scheme:// nor any /path/ or #fragment.)" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Connector" + }, + "dev.enola.core.meta.Data": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + }, + "type_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The fully qualified name of the (root) Protocol Buffer Message; see https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/any.proto" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Data" + }, + "dev.enola.core.meta.EntityKind": { + "properties": { + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID. The ns may be filled in by the reader, if omitted in *-model.textproto. The entity is the name of THIS EntityKind! This is typically never changed anymore after initial creation. The path contains' the segments' names (here, whereas in Entity it's the \"values\")." + }, + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label of entity, may be several words, any case. This can easily be changed at any time." + }, + "emoji": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The Emoji shown as prefix to the name in UIs, if there is no logo." + }, + "logo_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Logo (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "doc_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's textproto file location - from where a UI will serve it)." + }, + "related": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.EntityRelationship", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of related Entities. The string keys here match Entity#related's." + }, + "link": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Link", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of URL links. The string keys here match Entity#links's." + }, + "data": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Data", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Description of data about the Entity, in machine readable form. The string keys here match Entity#data's." + }, + "connectors": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "items": { + "$ref": "#/definitions/dev.enola.core.meta.Connector" + }, + "type": "array" + } + ] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Kind", + "description": "Kind of an Entity, as in message Entity." + }, + "dev.enola.core.meta.EntityRelationship": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "id": { + "$ref": "#/definitions/dev.enola.core.ID", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "ID reference to another Entity. This ID's ns/entity/paths fields can contain a template, like Link#uri_template. Alternatively, this can be left empty, and set by connectors." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Entity Relationship", + "description": "Entity#related map model; its key is the same as this in EntityKind#related." + }, + "dev.enola.core.meta.FileSystemRepository": { + "properties": { + "path": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "format": { + "enum": [ + "FORMAT_UNSPECIFIED", + 0, + "FORMAT_TEXTPROTO", + 1, + "FORMAT_YAML", + 2, + "FORMAT_JSON", + 3 + ], + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Format" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "File System Repository" + }, + "dev.enola.core.meta.Link": { + "properties": { + "label": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Human readable label, may be shown on a UI." + }, + "description": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short 1-2 sentences of description, may be shown on a UI e.g. as tooltip." + }, + "uri_template": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "URI template, to create URL. As an ID URI Template (RFC 6570); see https://en.wikipedia.org/wiki/URI_Template. The available variables are the ID's path parameters, as well as a special proto.* which allows to declaratively create links out of the Any proto (instead of coding link generation in the service; which is always still also possible)." + }, + "tags": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Tags." + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Link", + "description": "Entity#link map model; its key is the same as this in EntityKind#link." + }, + "dev.enola.core.meta.Property": { + "properties": { + "id": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "integer" + } + ] + }, + "link": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "title": "Type of Property, as Name of another Type.\n TODO Re-consider this... this is wrong, because Property is Types sharable.\n string type = 2 [(dev.enola.type) = \"enola.dev/type\"];", + "description": "Type of Property, as Name of another Type. TODO Re-consider this... this is wrong, because Property is Types sharable. string type = 2 [(dev.enola.type) = \"enola.dev/type\"]; Link to something related. Intended both for human consumption in a UI, as well as machine readable linked data relationships. Typically a Template of an (HTML a/href-like) HTTP URL, or enola: URI. Template parameters refer to other properties of the Type." + }, + "labels": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Human readable label of this property, may be several words, any case. This is a map where the key is an IETF [BCP 47](https://www.rfc-editor.org/info/bcp47) \"language code\" (like \"en\" or \"de-CH\") and the value is text in that language. These can easily be changed at any time without breaking anything in Enola." + }, + "doc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's location - from where a UI will serve it). TODO Is this a [(dev.enola.type) = \"enola.dev/url\"]? Really??" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Property" + }, + "dev.enola.core.meta.Type": { + "properties": { + "name": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Short technical name of this Type. Must be unique within the environment this Enola instance operates. Publicly, using something that looks like an IRI/URI/URL is a simple way for uniqueness. For example, \"your.org/something\" (which is technically a relative URI, by chance). This string is NOT (necessarily) a valid URL; e.g. you (generally) cannot \"http GET your.org/something\". As a convenience for humans which type this into their web browser, your.org MAY set up a \"redirector\" which responds with a 30x to somewhere \"interesting\" for a human (not a machine), but that's just \"nice\", nothing more. Enola will never use it as anything else than simply a unique string." + }, + "uri": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "title": "URL of this Type.\n For example, \"https://demo.enola.dev/type/enola.dev/Person\".\n You *CAN* http GET this URL. An Enola server will return a HTML page or\n JSON or something. This is NOT set by the author of the Type, but at\n runtime. It is based on the name.\n TODO string url = 3 [(dev.enola.type) = \"enola.dev/url\"];", + "description": "URL of this Type. For example, \"https://demo.enola.dev/type/enola.dev/Person\". You *CAN* http GET this URL. An Enola server will return a HTML page or JSON or something. This is NOT set by the author of the Type, but at runtime. It is based on the name. TODO string url = 3 [(dev.enola.type) = \"enola.dev/url\"]; URI Template of instances of this Type. For example, \"hello/{message}\" - where the parameter \"message\" refers to a property; so instances would be e.g. \"hello/world\" and \"hello/planets\"." + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dev.enola.core.meta.Property", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Properties of this Type. TODO This needs to be Reference to a Property, not a contained Property..." + }, + "labels": { + "additionalProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "description": "Human readable label of this type, may be several words, any case. This is a map where the key is an IETF [BCP 47](https://www.rfc-editor.org/info/bcp47) \"language code\" (like \"en\" or \"de-CH\") and the value is text in that language. These can easily be changed at any time without breaking anything in Enola." + }, + "doc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Documentation description (as URL; either absolute, or URL relative to the model's location - from where a UI will serve it). TODO Is this a [(dev.enola.type) = \"enola.dev/url\"]? Really??" + }, + "emoji": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "The Emoji shown as prefix to the label in UIs, if there is no logo. This is not necessarily unique." + }, + "logo_url": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Logo (as URL; either absolute, or URL relative to the model's location - from where a UI will serve it)." + }, + "legacy": { + "$ref": "#/definitions/dev.enola.core.meta.EntityKind", + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + {} + ], + "description": "EntityKind. This is from the original Enola design, and may later be removed. TODO Should this really be embedded? Really? Probably better an ID as reference? Good test! string entity_kind = 10 [(dev.enola.type) = \"enola.dev/EntityKind\"];" + }, + "proto": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ], + "description": "Protocol Buffers Message. For example, \"google.protobuf.Timestamp\". TODO We need to be able to \"annotate\" (?) Proto descriptors for LD..." + }, + "string": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Empty", + "description": "A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. A typical example is to use it as the request or the response type of an API method. For instance: service Foo { rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); }" + }, + "binary": { + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Empty", + "description": "A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. A typical example is to use it as the request or the response type of an API method. For instance: service Foo { rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); }" + }, + "java": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "javas": { + "items": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "oneOf": [ + { + "type": "null" + }, + { + "type": "array" + } + ] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ], + "title": "Type", + "description": "Unique ID of this Type. For example, \"d19974d6-0695-458d-bdd4-3ad89578db92\". This \"provides a relatively short yet unambiguous way to refer to a type\", as \"fully-qualified type names may be large and waste space when transmitted on the wire\", and it \"lets programmers change the symbolic name while keeping a fixed ID\" (inspired by https://capnproto.org/language.html#unique-ids). It's recommended that this is set by the human author of the Type, but if it's not, it will be automatically generated by hashing the name. (This defeats the purpose of a \"permanent\" ID - but it's at least possible to set it later, if a name is ever changed.) TODO \"Nicely render\" this in the Web UI, using dev.enola.core.ByteSeq. TODO Do we *really* this, actually? bytes id = 1 [(dev.enola.type) = \"enola.dev/id\"];" + } + } +} diff --git a/docs/models/geographical/README.md b/docs/models/geographical/README.md new file mode 100644 index 000000000..c13dce393 --- /dev/null +++ b/docs/models/geographical/README.md @@ -0,0 +1,25 @@ + + +# Geographical 🌍 Models + +## TODO + +* Proto with Lang & Lat?! +* Properties with link for Google Maps and Open Street View +* Geo Names? TODO and diff --git a/docs/models/github/github.types.yaml b/docs/models/github/github.types.yaml new file mode 100644 index 000000000..e277a8fad --- /dev/null +++ b/docs/models/github/github.types.yaml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# yaml-language-server: $schema=../enola/schemas/Types.schema.json + +# TODO Automatically build this from https://github.com/github/rest-api-description ?! + +types: + - name: enola.dev/github/repo + properties: + org: + repo: + homepage: + link: https://github.com/{org}/{repo} + + - name: enola.dev/github/issue + properties: + org: + repo: + issue: + ui: + link: https://github.com/{org}/{repo}/issues/{issue} + + - name: enola.dev/github/pull + properties: + org: + repo: + pr: + ui: + link: https://github.com/{org}/{repo}/pull/{pr} diff --git a/docs/use/connector/model-fs.yaml b/docs/use/connector/model-fs.yaml index a918bbf39..9493c711e 100644 --- a/docs/use/connector/model-fs.yaml +++ b/docs/use/connector/model-fs.yaml @@ -1,9 +1,9 @@ kinds: - - id: { ns: demo, entity: book_kind, paths: [ isbn ] } + - id: { ns: demo, entity: book_kind, paths: [isbn] } link: google: label: Google Book Search - uriTemplate: "https://www.google.com/search?tbm=bks&q={path.isbn}" + uriTemplate: "https://www.google.com/search?tbm=bks&q=isbn:{path.isbn}" data: authors: label: Names of the authors of this book. diff --git a/docs/use/library/model.yaml b/docs/use/library/model.yaml index 79cbe9bcf..db8d36953 100644 --- a/docs/use/library/model.yaml +++ b/docs/use/library/model.yaml @@ -5,7 +5,7 @@ kinds: link: google: label: Google Book Search - uriTemplate: "https://www.google.com/search?tbm=bks&q={path.isbn}" + uriTemplate: "https://www.google.com/search?tbm=bks&q=isbn:{path.isbn}" - id: { ns: demo, entity: library, paths: [id] } label: Library diff --git a/docs/use/rosetta/index.md b/docs/use/rosetta/index.md index ac2a5066c..6be433807 100644 --- a/docs/use/rosetta/index.md +++ b/docs/use/rosetta/index.md @@ -22,7 +22,11 @@ Rosetta, inspired by [the Rosetta Stone](https://en.wikipedia.org/wiki/Rosetta_Stone), transforms between [`YAML`](https://yaml.org) ⇔ [`JSON`](https://www.json.org) ⇔ [`TextProto`](https://protobuf.dev/reference/protobuf/textformat-spec/) ⇔ -_[Binary Protocol Buffer "Wire"](https://protobuf.dev/programming-guides/encoding/)_ formats: +_[Binary Protocol Buffer "Wire"](https://protobuf.dev/programming-guides/encoding/)_ formats. + +Specifying the `--schema` flag is optional for YAML <=> JSON conversion, but required for TextProto. + +For example: ```bash cd .././.././.. $ ./enola rosetta --in=file:docs/use/library/model.yaml --out=file:docs/use/library/model.json --schema=EntityKinds diff --git a/mkdocs.yaml b/mkdocs.yaml index ba3297943..44de5e48c 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -26,8 +26,11 @@ copyright: Copyright © 2023 The Enola Authors nav: - News (Blog): blog/index.md - Introduction: index.md + - Installation: use/index.md + - Models: + - Demo: + - Hello: models/demo/hello/README.md - Users: - - Overview: use/index.md - Help: use/help/index.md - Example Model: use/library/index.md - DocGen: use/docgen/index.md diff --git a/test.bash b/test.bash index e849d705b..22dfa3590 100755 --- a/test.bash +++ b/test.bash @@ -85,6 +85,9 @@ echo $ Bazel testing... # TODO Remove --nojava_header_compilation when https://github.com/bazelbuild/bazel/issues/21119 is fixed bazelisk query //... | xargs bazelisk test --nojava_header_compilation +# TODO Run all this only when model inputs change +tools/protoc/protoc.bash + # Test distros: 1. End-user distributed executable fat ΓΌber JAR, 2. Container Image tools/distro/test.bash diff --git a/tools/protoc/protoc.bash b/tools/protoc/protoc.bash new file mode 100755 index 000000000..e8c4bf802 --- /dev/null +++ b/tools/protoc/protoc.bash @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright 2023-2024 The Enola 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. + +# TODO Transform this into a Bazel target instead? +# Or an enola exec: resource? +# (Or no need, for either?) +set -euox pipefail + +# NB: protoc is installed by .devcontainer/devcontainer.json +# (e.g. from https://github.com/protocolbuffers/protobuf/releases) + +# https://github.com/chrusty/protoc-gen-jsonschema +if ! [ -x "$(command -v protoc-gen-jsonschema)" ]; then + go install github.com/chrusty/protoc-gen-jsonschema/cmd/protoc-gen-jsonschema@latest +fi + +PLUGIN=$(which protoc-gen-jsonschema) +protoc \ + --plugin="${PLUGIN}" \ + --jsonschema_opt=allow_null_values \ + --jsonschema_opt=file_extension=schema.json \ + --jsonschema_opt=disallow_additional_properties \ + --jsonschema_out=docs/models/enola/schemas/ \ + core/lib/src/main/java/dev/enola/core/meta/enola_meta.proto + +mkdir -pv ~/.npm/lib +npx --yes prettier --write docs/models/enola/schemas/*.json