diff --git a/.github/actions/install-rust-toolchain/action.yml b/.github/actions/install-rust-toolchain/action.yml new file mode 100644 index 00000000..b89712a6 --- /dev/null +++ b/.github/actions/install-rust-toolchain/action.yml @@ -0,0 +1,23 @@ +name: Install Rust Toolchain +description: | + Installs the Rust toolchain. + Relies on the fact that rustup show will install a toolchain. The installed version is based on a toolchain file, or stable if no such file is found. +inputs: + components: + description: A list of additional components to install + required: false + default: null +runs: + using: "composite" + steps: + - name: Install Rust Toolchain + # A note on using rustup show to do this (from https://rust-lang.github.io/rustup/overrides.html): + # To verify which toolchain is active, you can use rustup show, + # which will also try to install the corresponding toolchain if the current one has not been installed [...]. + # (Please note that this behavior is subject to change, as detailed in issue #1397 [https://github.com/rust-lang/rustup/issues/1397].) + run: rustup show + shell: bash + - name: Install Additional Components + run: rustup component add ${{ inputs.components }} + shell: bash + if: ${{ inputs.components != null }} \ No newline at end of file diff --git a/.github/workflows/notice-generation.yml b/.github/workflows/notice-generation.yml index e53183ab..ac1813d8 100644 --- a/.github/workflows/notice-generation.yml +++ b/.github/workflows/notice-generation.yml @@ -16,8 +16,8 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - - name: Install stable toolchain - run: rustup show + - name: Install toolchain + uses: ./.github/actions/install-rust-toolchain - name: Cache Dependencies uses: Swatinem/rust-cache@v1 - name: Generate the Notice diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index c5d5780f..6e7ea40e 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -21,23 +21,20 @@ jobs: submodules: recursive - name: Install protobuf-compiler run: sudo apt-get install -y protobuf-compiler - - name: Install nightly Rust toolchain - uses: actions-rs/toolchain@v1 + - name: Install .NET 7.0 + uses: actions/setup-dotnet@v3 with: - toolchain: nightly-2022-08-11 - default: true - override: true - components: clippy, rustfmt + dotnet-version: 7.0 + - name: Install Rust toolchain + uses: ./.github/actions/install-rust-toolchain + with: + components: clippy rustfmt - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - run: cargo check --workspace - # Note: cargo check should use the --locked option - # Excluding it because of this known issue: https://github.com/mozilla/uniffi-rs/issues/1032 - run: cargo clippy --all-targets --all-features --workspace --no-deps -- -D warnings - run: cargo fmt --all -- --check - name: Run doctest only - # we run doctests here as cargo tarpaulin (our test runner) - # requires nightly toolchain to do so uses: actions-rs/cargo@v1 with: command: test @@ -58,12 +55,12 @@ jobs: submodules: recursive - name: Install protobuf-compiler run: sudo apt-get install -y protobuf-compiler - - name: Install nightly Rust toolchain - uses: actions-rs/toolchain@v1 + - name: Install .NET 7.0 + uses: actions/setup-dotnet@v3 with: - toolchain: nightly-2022-08-11 - default: true - override: true + dotnet-version: 7.0 + - name: Install Rust toolchain + uses: ./.github/actions/install-rust-toolchain - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - name: Build diff --git a/Cargo.toml b/Cargo.toml index cc40e80f..43136353 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ members = [ # extension "core/module/managed_subscribe", + # DTDL tools + "dtdl-tools", + # digital twin model "digital-twin-model", - # DTDL parser - "dtdl-parser", - # samples "samples/common", "samples/protobuf_data_access", @@ -31,7 +31,7 @@ members = [ "samples/mixed", "samples/property", "samples/seat_massager", - "samples/streaming", + # "samples/streaming", ] [workspace.dependencies] diff --git a/README.md b/README.md index 0532aa85..81656c9f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ - [Cloning the Repo](#cloning-the-repo) - [Developer Notes](#developer-notes) - [JSON-LD Crate](#json-ld-crate) - - [DTDL Parser](#dtdl-parser) - [Building](#building) - [Running the Tests](#running-the-tests) - [Running the Samples](#running-the-samples) @@ -96,10 +95,6 @@ However, it currently has a build issue that is discussed [here](https://github. To work around this issue you will need to use git clone to obtain the source from [here](https://github.com/blast-hardcheese/json-ld) and checkout its "resolve-issue-40" branch. It should be cloned to a directory that is a sibling to ibeji. -### DTDL Parser - -There is no existing DTDL Parser for Rust, so we have provided a minimalistic one for DTDL v2 that is based on the [JavaScript DTDL Parser](https://github.com/Azure/azure-sdk-for-js/tree/%40azure/dtdl-parser_1.0.0-beta.2/sdk/digitaltwins/dtdl-parser). - ## Building Once you have installed the prerequisites, go to your enlistment's root directory and run: diff --git a/core/invehicle-digital-twin/src/main.rs b/core/invehicle-digital-twin/src/main.rs index 98d6d9b4..c35aa2bd 100644 --- a/core/invehicle-digital-twin/src/main.rs +++ b/core/invehicle-digital-twin/src/main.rs @@ -5,16 +5,14 @@ // Module references behind feature flags. Add any necessary module references here. // Start: Module references. -// Add a new feature to all() so the use statement is active for the feature. -// ex. #[cfg(all(feature = "feature_1", feature = "feature_2"))] -#[cfg(all(feature = "managed_subscribe"))] -use common::grpc_interceptor::GrpcInterceptorLayer; - #[cfg(feature = "managed_subscribe")] use managed_subscribe::managed_subscribe_module::ManagedSubscribeModule; // End: Module references. +#[allow(unused_imports)] +use common::grpc_interceptor::GrpcInterceptorLayer; + use common::grpc_server::GrpcServer; use core_protobuf_data_access::chariott::service_discovery::core::v1::service_registry_client::ServiceRegistryClient; use core_protobuf_data_access::chariott::service_discovery::core::v1::{ diff --git a/devops/cg/license_url_to_type.json b/devops/cg/license_url_to_type.json new file mode 100644 index 00000000..a4f02904 --- /dev/null +++ b/devops/cg/license_url_to_type.json @@ -0,0 +1,5 @@ +{ + "https://raw.githubusercontent.com/moq/moq4/main/License.txt": "BSD 3-Clause", + "https://www.nuget.org/packages/NUnit/3.13.3/License": "MIT", + "https://www.nuget.org/packages/NUnit.Analyzers/3.3.0/License": "MIT" +} \ No newline at end of file diff --git a/dtdl-parser/.accepted_words.txt b/dtdl-parser/.accepted_words.txt deleted file mode 100644 index a0e25ad9..00000000 --- a/dtdl-parser/.accepted_words.txt +++ /dev/null @@ -1,12 +0,0 @@ -api -com -DTDL -digitaltwins -dtdl -en -github -https -javascript -js -microsoft -sdk diff --git a/dtdl-parser/Cargo.toml b/dtdl-parser/Cargo.toml deleted file mode 100644 index 1347b654..00000000 --- a/dtdl-parser/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -[package] -name = "dtdl-parser" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -async-std = { workspace = true, features = ["attributes"] } -futures = { workspace = true } -generic-json = { workspace = true, features = ["serde_json-impl"] } -iref = { workspace = true } -json-ld = { git = "https://github.com/blast-hardcheese/json-ld", branch = "resolve-issue-40" } -lazy_static = { workspace = true } -log = { workspace = true } -regex = { workspace = true } -serde_json = { workspace = true } -strum = { workspace = true } -strum_macros = { workspace = true } - -[lib] -path = "src/lib.rs" -crate-type = ["lib"] \ No newline at end of file diff --git a/dtdl-parser/README.md b/dtdl-parser/README.md deleted file mode 100644 index 545000de..00000000 --- a/dtdl-parser/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# DTDL Parser - -## Overview - -This is a Rust DTDL Parser, based on the JavaScript DTDL Parser's [code](https://github.com/Azure/azure-sdk-for-js/tree/%40azure/dtdl-parser_1.0.0-beta.2/sdk/digitaltwins/dtdl-parser) -and [API](https://learn.microsoft.com/en-us/javascript/api/@azure/dtdl-parser). Currently, the Rust DTDL Parser only provides a subset of the functionality of the JavaScript DTDL Parser. diff --git a/dtdl-parser/dtdl/samples/building.json b/dtdl-parser/dtdl/samples/building.json deleted file mode 100644 index 500bc7ed..00000000 --- a/dtdl-parser/dtdl/samples/building.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@id": "dtmi:com:example:Building;1", - "@type": "Interface", - "displayName": "Building", - "contents": [ - { - "@type": "Property", - "name": "name", - "schema": "string", - "writable": true - }, - { - "@type": "Relationship", - "name": "contains", - "target": "dtmi:com:example:Room;1" - } - ], - "@context": "dtmi:dtdl:context;3" -} diff --git a/dtdl-parser/dtdl/samples/demo_resources.json b/dtdl-parser/dtdl/samples/demo_resources.json deleted file mode 100644 index 9f401c55..00000000 --- a/dtdl-parser/dtdl/samples/demo_resources.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC;1", - "contents": [ - { - "@type": ["Property", "Temperature", "RemotelyAccessible"], - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", - "name": "Cabin_AmbientAirTemperature", - "description": "The immediate surroundings air temperature (in Fahrenheit).", - "schema": "double", - "unit": "degreeFahrenheit", - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Get", "Set", "Subscribe", "Unsubscribe" ] - } - ] - }, - { - "@type": ["Command", "RemotelyAccessible"], - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:SendNotification;1", - "name": "HVAC_send_notification", - "request": { - "name": "send_notification", - "displayName": "send_notification", - "description": "Send a notification to the HVAC.", - "schema": "string" - }, - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Invoke" ] - } - ] - } - ] - } -] diff --git a/dtdl-parser/dtdl/samples/phone.json b/dtdl-parser/dtdl/samples/phone.json deleted file mode 100644 index 18c30fc9..00000000 --- a/dtdl-parser/dtdl/samples/phone.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@id": "dtmi:com:example:Phone;2", - "@type": "Interface", - "displayName": "Phone", - "contents": [ - { - "@type": "Component", - "name": "frontCamera", - "schema": "dtmi:com:example:Camera;3" - }, - { - "@type": "Component", - "name": "backCamera", - "schema": "dtmi:com:example:Camera;3" - }, - { - "@type": "Component", - "name": "deviceInfo", - "schema": "dtmi:azure:deviceManagement:DeviceInformation;2" - } - ], - "@context": "dtmi:dtdl:context;3" -} diff --git a/dtdl-parser/dtdl/samples/thermostat.json b/dtdl-parser/dtdl/samples/thermostat.json deleted file mode 100644 index 654fc7e9..00000000 --- a/dtdl-parser/dtdl/samples/thermostat.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@id": "dtmi:com:example:Thermostat;1", - "@type": "Interface", - "displayName": "Thermostat", - "contents": [ - { - "@type": "Telemetry", - "name": "temp", - "schema": "double" - }, - { - "@type": "Property", - "name": "setPointTemp", - "writable": true, - "schema": "double" - } - ], - "@context": "dtmi:dtdl:context;3" -} diff --git a/dtdl-parser/src/command_info.rs b/dtdl-parser/src/command_info.rs deleted file mode 100644 index 9585bad1..00000000 --- a/dtdl-parser/src/command_info.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::command_payload_info::CommandPayloadInfo; -use crate::content_info::ContentInfo; - -/// A command is a method that can be invoked on a digital twin. -pub trait CommandInfo: ContentInfo { - /// Returns the request. - fn request(&self) -> &Option>; - - /// Returns the response. - fn response(&self) -> &Option>; -} diff --git a/dtdl-parser/src/command_info_impl.rs b/dtdl-parser/src/command_info_impl.rs deleted file mode 100644 index 5afc6093..00000000 --- a/dtdl-parser/src/command_info_impl.rs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::command_info::CommandInfo; -use crate::command_payload_info::CommandPayloadInfo; -use crate::content_info::ContentInfo; -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::named_entity_info::NamedEntityInfo; - -pub struct CommandInfoImpl { - // EntityInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // NamedEntityInfo - name: Option, - - // CommandInfo - request: Option>, - response: Option>, -} - -impl CommandInfoImpl { - /// Returns a new CommandInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the command. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this command is defined. - /// * `defined_in` - The identifier of the partition in which this command is defined. - /// * `name` - The name. - /// * `request` - The request. - /// * `response` - The response. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - name: Option, - request: Option>, - response: Option>, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - name, - request, - response, - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for CommandInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::Command - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl NamedEntityInfo for CommandInfoImpl { - /// Returns the name. - fn name(&self) -> &Option { - &self.name - } -} - -impl ContentInfo for CommandInfoImpl {} - -impl CommandInfo for CommandInfoImpl { - /// Returns the request. - fn request(&self) -> &Option> { - &self.request - } - - /// Returns the response. - fn response(&self) -> &Option> { - &self.response - } -} - -#[cfg(test)] -mod command_info_impl_tests { - use super::*; - use crate::command_payload_info_impl::CommandPayloadInfoImpl; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use crate::primitive_schema_info_impl::PrimitiveSchemaInfoImpl; - use serde_json; - - #[test] - fn new_command_info_impl_test() -> Result<(), String> { - let id_result: Option = - create_dtmi("dtmi:com.example:command:HVAC:send_notification;1"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:HVAC;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let string_schema_info_id: Option = create_dtmi("dtmi:dtdl:class:String;2"); - assert!(string_schema_info_id.is_some()); - let string_schema_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - string_schema_info_id.unwrap(), - None, - None, - EntityKind::String, - )); - - let integer_schema_info_id: Option = create_dtmi("dtmi:dtdl:class:Integer;2"); - assert!(integer_schema_info_id.is_some()); - let integer_schema_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - integer_schema_info_id.unwrap(), - None, - None, - EntityKind::Integer, - )); - - let request_id: Option = create_dtmi("dtmi:com:example:send_notification:request:1"); - assert!(request_id.is_some()); - let request = Box::new(CommandPayloadInfoImpl::new( - DTDL_VERSION, - request_id.unwrap(), - None, - None, - None, - Some(string_schema_info), - )); - - let response_id: Option = - create_dtmi("dtmi:com:example:send_notification:response:1"); - assert!(response_id.is_some()); - let response = Box::new(CommandPayloadInfoImpl::new( - DTDL_VERSION, - response_id.unwrap(), - None, - None, - None, - Some(integer_schema_info), - )); - - let mut command_info = CommandInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(String::from("one")), - Some(request), - Some(response), - ); - command_info.add_undefined_property(String::from("first"), first_propery_value.clone()); - command_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(command_info.dtdl_version(), 2); - assert_eq!(command_info.id(), &id); - assert!(command_info.child_of().is_some()); - assert_eq!(command_info.child_of().clone().unwrap(), child_of); - assert!(command_info.defined_in().is_some()); - assert_eq!(command_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(command_info.entity_kind(), EntityKind::Command); - assert_eq!(command_info.undefined_properties().len(), 2); - assert_eq!( - command_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - command_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match command_info.name() { - Some(name) => assert_eq!(name, "one"), - None => return Err(String::from("name has not been set")), - } - - match command_info.request() { - Some(request) => { - assert_eq!(request.entity_kind(), EntityKind::CommandPayload); - match request.schema() { - Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::String), - None => return Err(String::from("request's schema has not been set")), - } - } - None => return Err(String::from("request has not been set")), - } - - match command_info.response() { - Some(response) => { - assert_eq!(response.entity_kind(), EntityKind::CommandPayload); - match response.schema() { - Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::Integer), - None => return Err(String::from("response's schema has not been set")), - } - } - None => return Err(String::from("request has not been set")), - } - - Ok(()) - } -} diff --git a/dtdl-parser/src/command_payload_info.rs b/dtdl-parser/src/command_payload_info.rs deleted file mode 100644 index 7e1ab1bc..00000000 --- a/dtdl-parser/src/command_payload_info.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::schema_field_info::SchemaFieldInfo; - -/// A command payload specifies the inputs and outputs for a command. -pub trait CommandPayloadInfo: SchemaFieldInfo {} diff --git a/dtdl-parser/src/command_payload_info_impl.rs b/dtdl-parser/src/command_payload_info_impl.rs deleted file mode 100644 index bef167ec..00000000 --- a/dtdl-parser/src/command_payload_info_impl.rs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::command_payload_info::CommandPayloadInfo; -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::named_entity_info::NamedEntityInfo; -use crate::schema_field_info::SchemaFieldInfo; -use crate::schema_info::SchemaInfo; - -pub struct CommandPayloadInfoImpl { - // EntityInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // NamedEntityInfo - name: Option, - - // SchemaFieldInfo - schema: Option>, -} - -impl CommandPayloadInfoImpl { - /// Returns a new CommandPayloadInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the command payload. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this command payload is defined. - /// * `defined_in` - The identifier of the partition in which this command payload is defined. - /// * `name` - The name. - /// * `schema` - The schema. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - name: Option, - schema: Option>, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - name, - schema, - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for CommandPayloadInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::CommandPayload - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifider. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl NamedEntityInfo for CommandPayloadInfoImpl { - /// Returns the name. - fn name(&self) -> &Option { - &self.name - } -} - -impl SchemaFieldInfo for CommandPayloadInfoImpl { - /// Returns the schema. - fn schema(&self) -> &Option> { - &self.schema - } -} - -impl CommandPayloadInfo for CommandPayloadInfoImpl {} - -#[cfg(test)] -mod command_payload_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use crate::primitive_schema_info_impl::PrimitiveSchemaInfoImpl; - use serde_json; - - #[test] - fn new_command_payload_info_impl_test() -> Result<(), String> { - let id_result: Option = create_dtmi("dtmi:com:example:send_notification:request;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:HVAC;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let schema_info_id: Option = create_dtmi("dtmi:dtdl:class:String;2"); - assert!(schema_info_id.is_some()); - - let boxed_schema_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - schema_info_id.unwrap(), - None, - None, - EntityKind::String, - )); - - let mut command_payload_info = CommandPayloadInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(String::from("one")), - Some(boxed_schema_info), - ); - command_payload_info - .add_undefined_property(String::from("first"), first_propery_value.clone()); - command_payload_info - .add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(command_payload_info.dtdl_version(), DTDL_VERSION); - assert_eq!(command_payload_info.id(), &id); - assert!(command_payload_info.child_of().is_some()); - assert_eq!(command_payload_info.child_of().clone().unwrap(), child_of); - assert!(command_payload_info.defined_in().is_some()); - assert_eq!(command_payload_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(command_payload_info.entity_kind(), EntityKind::CommandPayload); - assert!(command_payload_info.schema().is_some()); - match command_payload_info.schema() { - Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::String), - None => return Err(String::from("schema has not been set")), - } - - assert_eq!(command_payload_info.undefined_properties().len(), 2); - assert_eq!( - command_payload_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - command_payload_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match command_payload_info.name() { - Some(name) => assert_eq!(name, "one"), - None => return Err(String::from("name has not been set")), - } - - Ok(()) - } -} diff --git a/dtdl-parser/src/complex_schema_info.rs b/dtdl-parser/src/complex_schema_info.rs deleted file mode 100644 index 07c36870..00000000 --- a/dtdl-parser/src/complex_schema_info.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::schema_info::SchemaInfo; - -/// A complex schema is the base trait for all complex schemas, like object, map and array. -pub trait ComplexSchemaInfo: SchemaInfo {} diff --git a/dtdl-parser/src/component_info.rs b/dtdl-parser/src/component_info.rs deleted file mode 100644 index 09469211..00000000 --- a/dtdl-parser/src/component_info.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::content_info::ContentInfo; -use crate::interface_info::InterfaceInfo; - -/// A component specifies a reference to an interface. It allows interfaces to contain other interfaces. -pub trait ComponentInfo: ContentInfo { - /// Returns the interface, the component uses the term "schema" to refer to it. - fn schema(&self) -> &Option>; -} diff --git a/dtdl-parser/src/component_info_impl.rs b/dtdl-parser/src/component_info_impl.rs deleted file mode 100644 index cb3fb200..00000000 --- a/dtdl-parser/src/component_info_impl.rs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::component_info::ComponentInfo; -use crate::content_info::ContentInfo; -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::interface_info::InterfaceInfo; -use crate::named_entity_info::NamedEntityInfo; - -pub struct ComponentInfoImpl { - // EntityInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // NamedEntityInfo - name: Option, - - // ComponentInfo - schema: Option>, -} - -impl ComponentInfoImpl { - /// Returns a new ComponentInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the component. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this component is defined. - /// * `defined_in` - The identifier of the partition in which this component is defined. - /// * `name` - The name. - /// * `schema` - The interface. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - name: Option, - schema: Option>, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - name, - schema, - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for ComponentInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::Component - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl NamedEntityInfo for ComponentInfoImpl { - /// Returns the name. - fn name(&self) -> &Option { - &self.name - } -} - -impl ContentInfo for ComponentInfoImpl {} - -impl ComponentInfo for ComponentInfoImpl { - /// Returns the interface. - fn schema(&self) -> &Option> { - &self.schema - } -} - -#[cfg(test)] -mod component_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::interface_info_impl::InterfaceInfoImpl; - use crate::model_parser::DTDL_VERSION; - use serde_json; - - #[test] - fn new_component_info_impl_test() -> Result<(), String> { - let id_result: Option = create_dtmi("dtmi:com:example:Thermostat;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:Cabin;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example:Something;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let schema_info_id: Option = create_dtmi("dtmi:dtdl:class:String;2"); - assert!(schema_info_id.is_some()); - - let boxed_interface_info = - Box::new(InterfaceInfoImpl::new(DTDL_VERSION, schema_info_id.unwrap(), None, None)); - - let mut component_info = ComponentInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(String::from("one")), - Some(boxed_interface_info), - ); - component_info.add_undefined_property(String::from("first"), first_propery_value.clone()); - component_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(component_info.dtdl_version(), 2); - assert_eq!(component_info.id(), &id); - assert!(component_info.child_of().is_some()); - assert_eq!(component_info.child_of().clone().unwrap(), child_of); - assert!(component_info.defined_in().is_some()); - assert_eq!(component_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(component_info.entity_kind(), EntityKind::Component); - assert_eq!(component_info.undefined_properties().len(), 2); - assert_eq!( - component_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - component_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match component_info.name() { - Some(name) => assert_eq!(name, "one"), - None => return Err(String::from("name has not been set")), - } - - match component_info.schema() { - Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::Interface), - None => return Err(String::from("schema has not been set")), - } - - Ok(()) - } -} diff --git a/dtdl-parser/src/content_info.rs b/dtdl-parser/src/content_info.rs deleted file mode 100644 index 21a030da..00000000 --- a/dtdl-parser/src/content_info.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::named_entity_info::NamedEntityInfo; - -/// An abstract trait that represents entites that have content. -pub trait ContentInfo: NamedEntityInfo {} diff --git a/dtdl-parser/src/dtmi.rs b/dtdl-parser/src/dtmi.rs deleted file mode 100644 index 9bc6f23d..00000000 --- a/dtdl-parser/src/dtmi.rs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use iref::Iri; -use log::warn; -use regex::Regex; -use std::fmt; - -lazy_static! { - pub static ref DTMI_REGEX: Regex = - Regex::new(r"^dtmi:[^;]+(;[1-9][0-9]*(\.[0-9][1-9]*)?)?(#[^ ]*)?$").unwrap(); -} - -/// Digital Twin Model Identifier (DTMI). -#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)] -pub struct Dtmi { - value: String, - major_version: Option, - minor_version: Option, - versionless: String, - labels: Vec, - absolute_path: String, - fragment: String, -} - -impl Dtmi { - /// Returns a new DTMI instance. - /// - /// # Arguments - /// * `value` - The string representation of the DTMI. - pub fn new(value: &str) -> Result { - let new_iri_result = Iri::new(value); - if new_iri_result.is_err() { - return Err(format!("The value '{value}' does not represent a valid IRI")); - } - let iri = new_iri_result.unwrap(); - - let mut major_version: Option = None; - let mut minor_version: Option = None; - let absolute_path: String; - - let iri_path_parts: Vec<&str> = iri.path().into_str().split(';').collect(); - if iri_path_parts.len() == 1 { - // no version - absolute_path = String::from(iri_path_parts[0]); - } else if iri_path_parts.len() == 2 { - absolute_path = String::from(iri_path_parts[0]); - let version_parts: Vec<&str> = iri_path_parts[1].split('.').collect(); - if version_parts.len() == 1 { - // no minor version number - if let Ok(value) = version_parts[0].parse::() { - major_version = Some(value) - } - } else if version_parts.len() == 2 { - if let Ok(value) = version_parts[0].parse::() { - major_version = Some(value) - } - if let Ok(value) = version_parts[1].parse::() { - minor_version = Some(value) - } - } else { - return Err(format!("The value '{value}' has an invalid version")); - } - } else { - return Err(format!("The value '{value}' represents an invalid DTMI")); - } - - let versionless: String = format!("dtmi:{absolute_path}"); - - let labels: Vec = absolute_path.split(':').map(Into::into).collect(); - - let fragment = match iri.fragment() { - Some(fragment) => String::from(fragment.as_str()), - None => String::new(), - }; - - Ok(Self { - value: String::from(value), - major_version, - minor_version, - versionless, - labels, - absolute_path, - fragment, - }) - } - - /// Gets the string representation of the DTMI. - pub fn value(&self) -> &str { - &self.value - } - - /// Gets the major version of the DTMI. - pub fn major_version(&self) -> &Option { - &self.major_version - } - - /// Gets the minor version of the DTMI. - pub fn minor_version(&self) -> &Option { - &self.minor_version - } - - /// Gets the major and minor version of the DTMI. - pub fn complete_version(&self) -> f64 { - let major_version: f64 = match self.major_version { - Some(value) => value.into(), - None => 0.0, - }; - - let minor_version: f64 = match self.minor_version { - Some(value) => value.into(), - None => 0.0, - }; - - major_version + minor_version * 0.000001 - } - - /// Gets the portion of the DTMI that preceeds the version number. - pub fn versionless(&self) -> &str { - &self.versionless - } - - /// Gets the sequence of labels in the path portion of the DTMI. - pub fn labels(&self) -> &Vec { - &self.labels - } - - /// Gets the absolute path of the DTMI. - pub fn absolute_path(&self) -> &str { - &self.absolute_path - } - - /// Gets the name of the DTMI's fragment, which is the empty string if there is no fragment. - pub fn fragment(&self) -> &str { - &self.fragment - } -} - -impl fmt::Display for Dtmi { - /// Format support for DTMI. - /// - /// # Arguments - /// * `f` - The associated formatter. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.value()) - } -} - -/// Create a new DTMI instance. -/// -/// # Arguments -/// * `value` - The IRI to copy from. -pub fn create_dtmi(value: &str) -> Option { - if !DTMI_REGEX.is_match(value) { - warn!("The value '{value}' does not represent a valid DTMI"); - return None; - } - - let new_dtmi_result = Dtmi::new(value); - if let Err(error) = new_dtmi_result { - warn!("{error}"); - return None; - } - - Some(new_dtmi_result.unwrap()) -} - -#[cfg(test)] -mod dmti_tests { - use super::*; - - #[test] - fn new_dtmi_test() { - let mut new_dtmi_result = Dtmi::new("dtmi:com:example:Thermostat;1.234#some-fragment"); - assert!(new_dtmi_result.is_ok()); - let mut dtmi: Dtmi = new_dtmi_result.unwrap(); - assert!(dtmi.major_version().is_some()); - assert_eq!(dtmi.major_version().unwrap(), 1); - assert!(dtmi.minor_version().is_some()); - assert_eq!(dtmi.minor_version().unwrap(), 234); - assert_eq!(dtmi.complete_version(), 1.000234); - assert_eq!(dtmi.versionless(), "dtmi:com:example:Thermostat"); - assert_eq!(dtmi.labels().len(), 3); - assert_eq!(dtmi.labels()[0], "com"); - assert_eq!(dtmi.labels()[1], "example"); - assert_eq!(dtmi.labels()[2], "Thermostat"); - assert_eq!(dtmi.absolute_path, "com:example:Thermostat"); - assert_eq!(dtmi.fragment(), "some-fragment"); - assert_eq!(format!("{dtmi}"), "dtmi:com:example:Thermostat;1.234#some-fragment"); - - new_dtmi_result = Dtmi::new("dtmi:com:example:Thermostat;1.234#"); - assert!(new_dtmi_result.is_ok()); - dtmi = new_dtmi_result.unwrap(); - assert_eq!(dtmi.fragment(), ""); - assert_eq!(format!("{dtmi}"), "dtmi:com:example:Thermostat;1.234#"); - } - - #[test] - fn create_dtmi_test() { - let create_dtmi_result: Option = create_dtmi("dtmi:com:example:Thermostat;1.234567"); - assert!(create_dtmi_result.is_some()); - let dtmi = create_dtmi_result.unwrap(); - assert!(dtmi.major_version().is_some()); - assert!(dtmi.major_version().unwrap() == 1); - assert!(dtmi.minor_version().is_some()); - assert!(dtmi.minor_version().unwrap() == 234567); - assert_eq!(dtmi.complete_version(), 1.234567); - assert_eq!(dtmi.versionless(), "dtmi:com:example:Thermostat"); - assert_eq!(dtmi.labels().len(), 3); - assert_eq!(dtmi.labels()[0], "com"); - assert_eq!(dtmi.labels()[1], "example"); - assert_eq!(dtmi.labels()[2], "Thermostat"); - assert_eq!(dtmi.absolute_path, "com:example:Thermostat"); - } - - #[test] - fn bad_create_dtmi_test() { - let mut create_dtmi_result: Option = - create_dtmi("whatever:com:example:Thermostat;1.234567"); - assert!(create_dtmi_result.is_none()); - - create_dtmi_result = create_dtmi("dtmi:com:example:Thermostat;1.2.3"); - assert!(create_dtmi_result.is_none()); - - create_dtmi_result = create_dtmi("dtmi:;1.2"); - assert!(create_dtmi_result.is_none()); - } - - #[test] - fn eq_dtmi_test() { - let first_create_dtmi_result: Option = - create_dtmi("dtmi:com:example:Thermostat;1.234567"); - assert!(first_create_dtmi_result.is_some()); - let first_dtmi = first_create_dtmi_result.unwrap(); - - let second_create_dtmi_result: Option = - create_dtmi("dtmi:com:example:Thermostat;1.234567"); - assert!(second_create_dtmi_result.is_some()); - let second_dtmi = second_create_dtmi_result.unwrap(); - - let third_create_dtmi_result: Option = - create_dtmi("dtmi:com:example:Barometer;2.987"); - assert!(third_create_dtmi_result.is_some()); - let third_dtmi = third_create_dtmi_result.unwrap(); - - assert_eq!(first_dtmi, second_dtmi); - assert!(first_dtmi != third_dtmi); - } -} diff --git a/dtdl-parser/src/entity_info.rs b/dtdl-parser/src/entity_info.rs deleted file mode 100644 index ff21053f..00000000 --- a/dtdl-parser/src/entity_info.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::dtmi::Dtmi; -use crate::entity_kind::EntityKind; - -/// A digital twin model consists of building blocks known as entities. -/// EntityInfo is the base trait for all of the digital twin model building blocks. -pub trait EntityInfo: Any { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32; - - /// Returns the identifier of the DTDL element that corresponds to this object. - fn id(&self) -> &Dtmi; - - /// Returns the kind of Entity, meaning the concrete DTDL type assigned to the corresponding element in the model. - fn entity_kind(&self) -> EntityKind; - - /// Returns the identifier of the parent DTDL element in which this element is defined. - fn child_of(&self) -> &Option; - - /// Returns the identifier of the partition DTDL element in which this element is defined. - fn defined_in(&self) -> &Option; - - // Returns the description for this entity. - fn description(&self) -> &Option; - - // Returns the display name for this entity. - fn display_name(&self) -> &Option; - - /// Returns any undefined properties of the DTDL element that corresponds to this object. - fn undefined_properties(&self) -> &HashMap; - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any; -} diff --git a/dtdl-parser/src/entity_kind.rs b/dtdl-parser/src/entity_kind.rs deleted file mode 100644 index 2ff71022..00000000 --- a/dtdl-parser/src/entity_kind.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use strum_macros::Display; -use strum_macros::EnumIter; -use strum_macros::EnumString; - -/// Indicates the kind of Entity, meaning the concrete DTDL type assigned to the corresponding element in the model. -#[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, EnumIter, Display)] -pub enum EntityKind { - #[strum(serialize = "dtmi:dtdl:class:Array;2")] - Array, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:boolean;2")] - Boolean, - - #[strum(serialize = "dtmi:dtdl:class:Command;2")] - Command, - - #[strum(serialize = "dtmi:dtdl:class:CommandPayload;2")] - CommandPayload, - - #[strum(serialize = "dtmi:dtdl:class:CommandType;2")] - CommandType, - - #[strum(serialize = "dtmi:dtdl:class:Component;2")] - Component, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:date;2")] - Date, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:dateTime;2")] - DateTime, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:double;2")] - Double, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:duration;2")] - Duration, - - #[strum(serialize = "dtmi:dtdl:class:Enum;2")] - Enum, - - #[strum(serialize = "dtmi:dtdl:class:EnumValue;2")] - EnumValue, - - #[strum(serialize = "dtmi:dtdl:class:Field;2")] - Field, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:float;2")] - Float, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:integer;2")] - Integer, - - #[strum(serialize = "dtmi:dtdl:class:Interface;2")] - Interface, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:long;2")] - Long, - - #[strum(serialize = "dtmi:dtdl:class:Map;2")] - Map, - - #[strum(serialize = "dtmi:dtdl:class:MapKey;2")] - MapKey, - - #[strum(serialize = "dtmi:dtdl:class:MapValue;2")] - MapValue, - - #[strum(serialize = "dtmi:dtdl:class:Object;2")] - Object, - - #[strum(serialize = "dtmi:dtdl:class:Property;2")] - Property, - - #[strum(serialize = "dtmi:dtdl:class:Relationship;2")] - Relationship, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:string;2")] - String, - - #[strum(serialize = "dtmi:dtdl:class:Telemetry;2")] - Telemetry, - - #[strum(serialize = "dtmi:dtdl:instance:Schema:time;2")] - Time, - - #[strum(serialize = "dtmi:dtdl:class:Unit;2")] - Unit, - - #[strum(serialize = "dtmi:dtdl:class:UnitAttribute;2")] - UnitAttribute, - - #[strum(serialize = "dtmi:dtdl:class:CommandRequest;2")] - CommandRequest, - - #[strum(serialize = "dtmi:dtdl:class:CommandResponse;2")] - CommandResponse, - - #[strum(serialize = "dtmi:dtdl:class:LatentType;2")] - LatentType, - - #[strum(serialize = "dtmi:dtdl:class:NamedLatentType;2")] - NamedLatentType, - - #[strum(serialize = "dtmi:dtdl:class:Reference;2")] - Reference, -} diff --git a/dtdl-parser/src/field_info.rs b/dtdl-parser/src/field_info.rs deleted file mode 100644 index a5e628ef..00000000 --- a/dtdl-parser/src/field_info.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::schema_field_info::SchemaFieldInfo; - -/// Specifies a field. -pub trait FieldInfo: SchemaFieldInfo {} diff --git a/dtdl-parser/src/field_info_impl.rs b/dtdl-parser/src/field_info_impl.rs deleted file mode 100644 index c9b4bb75..00000000 --- a/dtdl-parser/src/field_info_impl.rs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::field_info::FieldInfo; -use crate::named_entity_info::NamedEntityInfo; -use crate::schema_field_info::SchemaFieldInfo; -use crate::schema_info::SchemaInfo; - -pub struct FieldInfoImpl { - // EntityInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // NamedEntityInfo - name: Option, - - // SchemaFieldInfo - schema: Option>, -} - -impl FieldInfoImpl { - /// Returns a new FieldInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the field. - /// * `id` - The identifier.. - /// * `child_of` - The identifier of the parent element in which this field is defined. - /// * `defined_in` - The identifier of the partition in which this field is defined. - /// * `name` - The name. - /// * `schema` - The schema. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - name: Option, - schema: Option>, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - name, - schema, - } - } - - /// Set the display name. - /// - /// # Arguments - /// `display_name` - The new display name. - pub fn set_display_name(&mut self, display_name: Option) { - self.display_name = display_name; - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for FieldInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::Telemetry - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl NamedEntityInfo for FieldInfoImpl { - /// Returns the name of the field. - fn name(&self) -> &Option { - &self.name - } -} - -impl SchemaFieldInfo for FieldInfoImpl { - /// Returns the schema. - fn schema(&self) -> &Option> { - &self.schema - } -} - -impl FieldInfo for FieldInfoImpl {} - -#[cfg(test)] -mod field_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use crate::primitive_schema_info_impl::PrimitiveSchemaInfoImpl; - use serde_json; - - #[test] - fn new_field_info_impl_test() -> Result<(), String> { - let id_result: Option = create_dtmi("dtmi:com:example:Field;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:Cabin;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let schema_info_id: Option = create_dtmi("dtmi:dtdl:class:String;2"); - assert!(schema_info_id.is_some()); - - let boxed_schema_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - schema_info_id.unwrap(), - None, - None, - EntityKind::String, - )); - - let mut field_info = FieldInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(String::from("one")), - Some(boxed_schema_info), - ); - field_info.add_undefined_property(String::from("first"), first_propery_value.clone()); - field_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(field_info.dtdl_version(), 2); - assert_eq!(field_info.id(), &id); - assert!(field_info.child_of().is_some()); - assert_eq!(field_info.child_of().clone().unwrap(), child_of); - assert!(field_info.defined_in().is_some()); - assert_eq!(field_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(field_info.entity_kind(), EntityKind::Telemetry); - assert_eq!(field_info.undefined_properties().len(), 2); - assert_eq!( - field_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - field_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match field_info.name() { - Some(name) => assert_eq!(name, "one"), - None => return Err(String::from("name has not been set")), - } - - match field_info.schema() { - Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::String), - None => return Err(String::from("schema has not been set")), - } - - Ok(()) - } -} diff --git a/dtdl-parser/src/interface_info.rs b/dtdl-parser/src/interface_info.rs deleted file mode 100644 index 7b63776b..00000000 --- a/dtdl-parser/src/interface_info.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::entity_info::EntityInfo; - -/// An interface specifies a collection of Commands, Components, Properties, Relationships and Telemetries. -pub trait InterfaceInfo: EntityInfo {} diff --git a/dtdl-parser/src/interface_info_impl.rs b/dtdl-parser/src/interface_info_impl.rs deleted file mode 100644 index ec64adca..00000000 --- a/dtdl-parser/src/interface_info_impl.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::interface_info::InterfaceInfo; - -#[derive(Clone)] -pub struct InterfaceInfoImpl { - // EntitytInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, -} - -impl InterfaceInfoImpl { - /// Returns a new InterfaceInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the interface. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this interface is defined. - /// * `defined_in` - The identifier of the partition in which this interface is defined. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for InterfaceInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::Interface - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl InterfaceInfo for InterfaceInfoImpl {} - -#[cfg(test)] -mod interface_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use serde_json; - - #[test] - fn new_interface_info_impl_test() { - let id_result: Option = create_dtmi("dtmi:com:example:my_interface;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:vehicle;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let mut interface_info = InterfaceInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - ); - interface_info.add_undefined_property(String::from("first"), first_propery_value.clone()); - interface_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(interface_info.dtdl_version(), 2); - assert_eq!(interface_info.id(), &id); - assert!(interface_info.child_of().is_some()); - assert_eq!(interface_info.child_of().clone().unwrap(), child_of); - assert!(interface_info.defined_in().is_some()); - assert_eq!(interface_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(interface_info.entity_kind(), EntityKind::Interface); - assert_eq!(interface_info.undefined_properties().len(), 2); - assert_eq!( - interface_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - interface_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - } -} diff --git a/dtdl-parser/src/lib.rs b/dtdl-parser/src/lib.rs deleted file mode 100644 index 61a1ca45..00000000 --- a/dtdl-parser/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -#[macro_use] -extern crate lazy_static; - -extern crate json_ld; - -pub mod command_info; -pub mod command_info_impl; -pub mod command_payload_info; -pub mod command_payload_info_impl; -pub mod complex_schema_info; -pub mod component_info; -pub mod component_info_impl; -pub mod content_info; -pub mod dtmi; -pub mod entity_info; -pub mod entity_kind; -pub mod field_info; -pub mod field_info_impl; -pub mod interface_info; -pub mod interface_info_impl; -pub mod model_dict; -pub mod model_parser; -pub mod named_entity_info; -pub mod object_info; -pub mod object_info_impl; -pub mod primitive_schema_info; -pub mod primitive_schema_info_impl; -pub mod primitive_schema_kinds; -pub mod property_info; -pub mod property_info_impl; -pub mod relationship_info; -pub mod relationship_info_impl; -pub mod schema_field_info; -pub mod schema_info; -pub mod telemetry_info; -pub mod telemetry_info_impl; diff --git a/dtdl-parser/src/model_dict.rs b/dtdl-parser/src/model_dict.rs deleted file mode 100644 index 182bf0f1..00000000 --- a/dtdl-parser/src/model_dict.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; - -use std::collections::HashMap; - -pub type ModelDict = HashMap>; diff --git a/dtdl-parser/src/model_parser.rs b/dtdl-parser/src/model_parser.rs deleted file mode 100644 index 6b8216ef..00000000 --- a/dtdl-parser/src/model_parser.rs +++ /dev/null @@ -1,1175 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use futures::executor::block_on; -use json_ld::{context, Document, NoLoader, Node, Object}; -use log::warn; -use serde_json::{Map, Value}; -use std::collections::HashMap; -use std::env; -use std::fs; -use std::path::Path; -use std::str::FromStr; -use strum::IntoEnumIterator; - -use crate::command_info_impl::CommandInfoImpl; -use crate::command_payload_info::CommandPayloadInfo; -use crate::command_payload_info_impl::CommandPayloadInfoImpl; -use crate::component_info_impl::ComponentInfoImpl; -use crate::dtmi::{create_dtmi, Dtmi}; -use crate::entity_kind::EntityKind; -use crate::field_info::FieldInfo; -use crate::field_info_impl::FieldInfoImpl; -use crate::interface_info::InterfaceInfo; -use crate::interface_info_impl::InterfaceInfoImpl; -use crate::json_ld::util::AsJson; -use crate::model_dict::ModelDict; -use crate::object_info_impl::ObjectInfoImpl; -use crate::primitive_schema_info_impl::PrimitiveSchemaInfoImpl; -use crate::primitive_schema_kinds::is_primitive_schema_kind; -use crate::property_info_impl::PropertyInfoImpl; -use crate::relationship_info_impl::RelationshipInfoImpl; -use crate::schema_info::SchemaInfo; -use crate::telemetry_info_impl::TelemetryInfoImpl; - -/// The DTDL Version that the parser supports. -pub const DTDL_VERSION: i32 = 2; - -/// Instances of the ModelParser class parse models written in the DTDL language. -/// This class can be used to determine: whether one or more DTDL models are valid, -/// to identify specific modeling errors, and to enable inspection of model contents. -pub struct ModelParser {} - -impl Default for ModelParser { - fn default() -> Self { - Self::new() - } -} - -impl ModelParser { - /// The DTDL-path environment variable name. - pub const DTDL_PATH: &str = "DTDL_PATH"; - - /// Returns a new ModelParser instance. - pub fn new() -> Self { - Self {} - } - - /// Parse a list of JSON texts and return the resulting model dictionary. - /// - /// # Arguments - /// * `json_texts` - A list of JSON texts. - pub fn parse(&mut self, json_texts: &Vec) -> Result { - let mut model_dict: ModelDict = ModelDict::new(); - - self.add_primitive_schemas_to_model_dict(&mut model_dict)?; - - // Add the entries to the model dictionary for the primitive entity kinds. - for entity_kind in EntityKind::iter() { - if is_primitive_schema_kind(entity_kind) { - let schema_info_id: Option = create_dtmi(&entity_kind.to_string()); - if schema_info_id.is_none() { - return Err(format!( - "Cannot form a valid schema id for primitive entity kind '{entity_kind}." - )); - } - - let boxed_entity_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - schema_info_id.clone().unwrap(), - None, - None, - entity_kind, - )); - model_dict.insert(schema_info_id.clone().unwrap(), boxed_entity_info); - } - } - - for json_text in json_texts { - let mut doc: Value = match serde_json::from_str(json_text) { - Ok(json) => json, - Err(error) => { - return Err(format!("Failed to parse one of the JSON texts due to: {error}")) - } - }; - - self.preprocess(&mut doc)?; - - let mut loader = NoLoader::::new(); - let dtdl_doc = - block_on(doc.expand::, _>(&mut loader)).map_err(|error| { - format!("Failed to expand one of the JSON texts due to: {error:?}") - })?; - - for item in dtdl_doc.iter() { - let object: &Object = item; - if let Object::Node(node) = object { - self.parse_node(node, &None, &mut model_dict)?; - } - } - } - - Ok(model_dict) - } - - /// Add the entries to the model dictionary for the primitive schemas. - /// - /// # Arguments - /// * `model_dict` - The model dictionary. - fn add_primitive_schemas_to_model_dict( - &mut self, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - for entity_kind in EntityKind::iter() { - if is_primitive_schema_kind(entity_kind) { - let schema_info_id: Option = create_dtmi(&entity_kind.to_string()); - if schema_info_id.is_none() { - return Err(format!( - "Cannot form a valid schema id for primitive schema {entity_kind}." - )); - } - - let boxed_entity_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - schema_info_id.clone().unwrap(), - None, - None, - entity_kind, - )); - model_dict.insert(schema_info_id.clone().unwrap(), boxed_entity_info); - } - } - - Ok(()) - } - - /// Retrieve a JSON-LD context as a JSON object from the location specified in the filepath. - /// - /// # Arguments - /// * `filepath` - The file path where the context is located. - fn retrieve_context(&mut self, filepath: &Path) -> Result { - let contents: String = match fs::read_to_string(filepath) { - Ok(data) => data, - Err(error) => { - return Err(format!( - "Unable to read the context located at {} due to: {:?}", - filepath.display(), - error - )) - } - }; - - let doc: Value = match serde_json::from_str(&contents) { - Ok(json) => json, - Err(error) => { - return Err(format!( - "Unable to parse the context located at {} due to: {:?}", - filepath.display(), - error - )) - } - }; - - Ok(doc) - } - - /// Replace a name reference for a context in a JSON object with its respective JSON value. - /// - /// # Arguments - /// * `obj` - The JSON object represented as a map of names to JSON objects. - /// * `context_name` - The name of the context that we want to replace. - /// * 'context_value` - The JSON object that we will replace it with. - #[allow(clippy::needless_range_loop)] - fn replace_context_inline_in_object( - &mut self, - obj: &mut Map, - context_name: &str, - context_value: &Value, - ) -> Result<(), String> { - let existing_context_value_option = obj.get_mut("@context"); - if let Some(existing_context_value) = existing_context_value_option { - if let Value::String(s) = existing_context_value { - if s == context_name { - obj.remove("@context"); - obj.insert(String::from("@context"), context_value.clone()); - } - } else if let Value::Array(a) = existing_context_value { - for i in 0..a.len() { - if let Value::String(_) = &a[i] { - if a[i] == context_name { - a[i] = context_value.clone(); - break; - } - } - } - } else if let Value::Object(_o) = existing_context_value { - // ignore - this one does not have an IRI associated with it. - } else { - return Err(format!("Unexpected context value '{existing_context_value:?}'")); - } - } - Ok(()) - } - - /// Replace a name reference for a context in a JSON-LD document with its respective JSON value. - /// - /// # Arguments - /// * `doc` - The JSON document. - /// * `context_name` - The name of the context that we want to replace. - /// * 'context_value` - The JSON object that we will replace it with. - fn replace_context_inline_in_doc( - &mut self, - doc: &mut Value, - context_name: &str, - context_value: &Value, - ) -> Result<(), String> { - match doc { - Value::Array(array) => { - for v in array.iter_mut() { - self.replace_context_inline_in_doc(v, context_name, context_value)?; - } - } - Value::Object(object) => { - self.replace_context_inline_in_object(object, context_name, context_value)?; - } - _ => warn!("An unexpected json value was encountered"), - } - Ok(()) - } - - /// Preprocess a JSON-LD document, so that supported dtmi contexts will have their names replaced by their respective JSON. - /// - /// # Arguments - /// * `doc` - The JSON-LD document to preprocess. - /// - /// # Examples of supported context formats: - /// - /// "@context": "https://json-ld.org/contexts/person.json" - /// - /// "@context": [ - /// "https://json-ld.org/contexts/person.json", - /// "https://json-ld.org/contexts/place.json", - /// {"title": "http://purl.org/dc/terms/title"} - /// ] - fn preprocess(&mut self, doc: &mut Value) -> Result<(), String> { - let dtdl_2_context_path_string = Self::find_full_path("v2/context/DTDL.v2.context.json")?; - let dtdl_2_context_path_string_unwrapped = dtdl_2_context_path_string; - let dtdl_2_context_path = Path::new(&dtdl_2_context_path_string_unwrapped); - let dtdl_2_context_value = self.retrieve_context(dtdl_2_context_path)?; - self.replace_context_inline_in_doc(doc, "dtmi:dtdl:context;2", &dtdl_2_context_value)?; - - Ok(()) - } - - /// Get a property value from a node by name. - /// - /// # Arguments - /// * `node` - The node that contains the property. - /// * `property_name` - The name of the property. - fn get_property_value( - &self, - node: &Node, - property_name: &str, - ) -> Result, String> { - for (the_property, the_objects) in node.properties() { - if the_property == property_name { - if the_objects.len() == 1 { - match the_objects[0].as_str() { - Some(v) => return Ok(Some(String::from(v))), - None => { - return Err(String::from( - "get_property_value was unable to convert the value to a str", - )) - } - } - } else { - return Err(String::from( - "get_property_value does not contain the expected number of objects", - )); - } - } - } - - Ok(None) - } - - /// Get the schema info for a primary or existing schema. Both are represented by a schema name that could represent either case. - /// This function will determine which one it is and return the corresponding schema info. - /// - /// # Arguments - /// * `node` - The node that contains the schema's name. - /// * `model_dict` - The model dictionary, containing the schema infos that have already been captured. - /// * `parent_id` - The parent id. - fn get_primary_or_existing_schema( - &self, - node: &Node, - model_dict: &mut ModelDict, - parent_id: &Option, - ) -> Result, String> { - let string_option: Option<&str> = node.as_str(); - if let Some(schema_name) = string_option { - let entity_kind_option: Option = match EntityKind::from_str(schema_name) { - Ok(v) => Some(v), - Err(_) => None, - }; - - if let Some(entity_kind) = entity_kind_option { - if is_primitive_schema_kind(entity_kind) { - let id: Option = self.generate_id(parent_id, "test"); - if id.is_none() { - return Err(String::from( - "we were not able to generate an id for the schema", - )); - } - - Ok(Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - id.unwrap(), - parent_id.clone(), - None, - entity_kind, - ))) - } else { - Err(format!("expected a primitive schema, found {entity_kind}")) - } - } else { - self.retrieve_schema_info_from_model_dict(schema_name, model_dict) - } - } else { - Err(String::from("get_schema encountered an unknown entity kind value")) - } - } - - /// Get an object schema info from a node. - /// - /// # Arguments - /// * `node` - The node that contains the object schema's specification. - /// * `model_dict` - The model dictionary, containing the schema infos that have already been captured. - /// * `parent_id` - The parent id. - fn get_object_schema( - &self, - node: &Node, - model_dict: &mut ModelDict, - parent_id: &Option, - ) -> Result, String> { - let mut fields: Vec> = Vec::new(); - - for (the_property, the_objects) in node.properties() { - if the_property == "dtmi:dtdl:property:fields;2" { - for i in 0..the_objects.len() { - if let Object::Node(node) = &*the_objects[i] { - let mut name_option: Option = None; - let mut display_name_option: Option = None; - let mut schema: Option> = None; - for (the_property, the_objects) in node.properties() { - if the_property == "dtmi:dtdl:property:displayName;2" - && the_objects.len() == 1 - { - if let Object::Value(value) = &*the_objects[0] { - display_name_option = value.as_str().map(String::from) - } - } else if the_property == "dtmi:dtdl:property:schema;2" - && the_objects.len() == 1 - { - if let Object::Node(node) = &*the_objects[0] { - if node.properties().is_empty() { - schema = Some(self.get_primary_or_existing_schema( - node, model_dict, parent_id, - )?); - } else { - schema = Some( - self.get_complex_schema(node, model_dict, parent_id)?, - ); - } - } - } else if the_property == "dtmi:dtdl:property:name;2" - && the_objects.len() == 1 - { - if let Object::Value(value) = &*the_objects[0] { - name_option = value.as_str().map(String::from) - } - } - } - if name_option.is_some() { - let id: Option = - self.generate_id(parent_id, &name_option.clone().unwrap()); - if id.is_none() { - return Err(String::from( - "We were not able to generate an id for the schema.", - )); - } - - let mut field_info = FieldInfoImpl::new( - DTDL_VERSION, - id.unwrap(), - parent_id.clone(), - None, - name_option, - schema, - ); - - field_info.set_display_name(display_name_option); - - fields.push(Box::new(field_info)); - } - } - } - } - } - - let id: Option = self.generate_id(parent_id, "test"); - if id.is_none() { - return Err(String::from("We were not able to generate an id for the schema.")); - } - - Ok(Box::new(ObjectInfoImpl::new( - DTDL_VERSION, - id.unwrap(), - parent_id.clone(), - None, - Some(fields), - ))) - } - - /// Get a complex schema info from a node. - /// - /// # Arguments - /// * `node` - The node that contains the complex schema's specification. - /// * `model_dict` - The model dictionary, containing the schema infos that have already been captured. - /// * `parent_id` - The parent id. - fn get_complex_schema( - &self, - node: &Node, - model_dict: &mut ModelDict, - parent_id: &Option, - ) -> Result, String> { - let mut entity_kind_option: Option = None; - for node_type in node.types() { - let entity_kind_result = EntityKind::from_str(node_type.as_str()); - if let Ok(entity_kind) = entity_kind_result { - entity_kind_option = Some(entity_kind); - break; - } - } - - if entity_kind_option.is_none() { - return Err(String::from("Complex schema has no associated type. It must have one.")); - } - - let entity_kind = entity_kind_option.unwrap(); - - if entity_kind == EntityKind::Object { - self.get_object_schema(node, model_dict, parent_id) - } else { - Err(format!("Unsupported complex object: {entity_kind:?}.")) - } - } - - /// Get a schema info from a node. - /// - /// # Arguments - /// * `node` - The node that contains the schema's specification. - /// * `model_dict` - The model dictionary, containing the schema infos that have already been captured. - /// * `parent_id` - The parent id. - fn get_schema( - &self, - node: &Node, - model_dict: &mut ModelDict, - parent_id: &Option, - ) -> Result, String> { - for (the_property, the_objects) in node.properties() { - if the_property == "dtmi:dtdl:property:schema;2" { - if the_objects.len() == 1 { - if let Object::Node(node) = &*the_objects[0] { - if node.properties().is_empty() { - return self - .get_primary_or_existing_schema(node, model_dict, parent_id); - } else { - return self.get_complex_schema(node, model_dict, parent_id); - } - } else { - return Err(String::from( - "The schema property's associated object should be a node. It is not.", - )); - } - } else { - return Err(format!( - "The schema property should only have 1 assoicated object. It has {}.", - the_objects.len() - )); - } - } - } - - Err(String::from("A schema property was not found.")) - } - - /// Get the payload. - /// - /// # Arguments - /// * `node` - The node that contains the payload's specification. - /// * `model_dict` - The model dictionary, containing the schema infos that have already been captured. - /// * `property_name` - The property name associated with the payload. - /// * `parent_id` - The parent id. - fn get_payload( - &self, - node: &Node, - model_dict: &mut ModelDict, - property_name: &str, - parent_id: &Option, - ) -> Result>, String> { - for (the_property, the_objects) in node.properties() { - if the_property == property_name { - if let Object::Node(node) = &*the_objects[0] { - // name - optional - let name = self.get_property_value(node, "dtmi:dtdl:property:name;2")?; - - let mut id: Option = None; - if node.id().is_some() { - id = create_dtmi(node.id().unwrap().as_str()); - } - if id.is_none() { - if name.is_none() { - return Err(String::from( - "We cannot generate an id for the payload when we do not have a name.", - )); - } - id = self.generate_id(parent_id, &name.clone().unwrap()); - if id.is_none() { - return Err(String::from( - "We were unable to generate an id for the payload.", - )); - } - } - - // displayName - required - let _display_name = - self.get_property_value(node, "dtmi:dtdl:property:displayName;2")?; - - // description - required - let _description = - self.get_property_value(node, "dtmi:dtdl:property:description;2")?; - - // schema - required - let boxed_schema_info: Box = - self.get_schema(node, model_dict, &id)?; - - return Ok(Some(Box::new(CommandPayloadInfoImpl::new( - DTDL_VERSION, - id.unwrap(), - parent_id.clone(), - None, - name, - Some(boxed_schema_info), - )))); - } else { - return Err(String::from("get_payload encountered an unknown object")); - } - } - } - - Ok(None) - } - - /// Gather the undefined propeties from a node. - /// - /// # Arguments - /// * `node` - The node to gather the undefined properties from. - /// * `undefined_properties` - The resulting gathered undefined properties. - fn gather_undefined_properties( - node: &Node, - undefined_properties: &mut HashMap, - ) { - for (the_property, the_objects) in node.properties() { - if the_objects.len() == 1 { - match &*the_objects[0] { - Object::Value(value) => { - let j = value.clone().as_json(); - undefined_properties.insert(the_property.to_string(), j); - } - Object::Node(n) => { - Self::gather_undefined_properties(n, undefined_properties); - } - Object::List(_list) => { - warn!("gather_undefined_properties encountered a list"); - } - } - } - } - } - - /// Genrate an id from the associated parent id and the associated property name. - /// - /// # Arguments - /// * `parent_id` - The associated parent id. - /// * `name` - The associated property name. - fn generate_id(&self, parent_id: &Option, name: &str) -> Option { - let generated_id_value = format!("{}:{}", parent_id.clone().unwrap().versionless(), name); - create_dtmi(&generated_id_value) - } - - /// Retrieve a schema info from a dictionary. - /// - /// # Arguments - /// * `schema` - The id (as a string) for the schema info. - /// * `model_dict` - The model dictionary to search. - fn retrieve_schema_info_from_model_dict( - &self, - schema: &str, - model_dict: &mut ModelDict, - ) -> Result, String> { - let primitive_schema_info_id: Option = create_dtmi(schema); - if primitive_schema_info_id.is_none() { - return Err(String::from("Primitive schema cannot form a valid schema id.")); - } - let primitive_schema_info_model_entry = - model_dict.get(&primitive_schema_info_id.clone().unwrap()); - if primitive_schema_info_model_entry.is_none() { - return Err(format!( - "We were not able to find the primitive schema entry for id '{}'.", - primitive_schema_info_id.unwrap() - )); - } - let boxed_primitive_schema_info_ref_result = primitive_schema_info_model_entry - .unwrap() - .as_any() - .downcast_ref::(); - let boxed_schema_info: Box = match boxed_primitive_schema_info_ref_result { - Some(boxed_primitive_schema_info_ref) => { - Box::new((*boxed_primitive_schema_info_ref).clone()) - } - None => return Err(String::from("Was not a primitive schema info")), - }; - - Ok(boxed_schema_info) - } - - /// Retrieve an interface info from a model dictionary. - /// - /// # Arguments - /// * `schema` - The id (as a string) for the interface info. - /// * `model_dict` - The model dictionary to search. - fn retrieve_interface_info_from_model_dict( - &mut self, - schema: &str, - model_dict: &mut ModelDict, - ) -> Result, String> { - let interface_info_id: Option = create_dtmi(schema); - if interface_info_id.is_none() { - return Err(String::from("Schema cannot form a valid schema id.")); - } - let interface_info_model_entry = model_dict.get(&interface_info_id.clone().unwrap()); - if interface_info_model_entry.is_none() { - return Err(format!( - "We were not able to find the interface entry for id '{}'.", - interface_info_id.unwrap() - )); - } - let boxed_interface_schema_info_ref_result = - interface_info_model_entry.unwrap().as_any().downcast_ref::(); - let boxed_interface_info: Box = - match boxed_interface_schema_info_ref_result { - Some(boxed_interface_schema_info_ref) => { - Box::new((*boxed_interface_schema_info_ref).clone()) - } - None => return Err(String::from("Was not an interface info")), - }; - - Ok(boxed_interface_info) - } - - /// Parse a node. - /// - /// # Arguments - /// * `node` - The node to parse. - /// * `model_dict` - The model dictionary to add the content to. - fn parse_node( - &mut self, - node: &Node, - parent_id: &Option, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - let mut entity_kind_option: Option = None; - for node_type in node.types() { - let entity_kind_result = EntityKind::from_str(node_type.as_str()); - if let Ok(entity_kind) = entity_kind_result { - entity_kind_option = Some(entity_kind); - break; - } - } - - if entity_kind_option.is_none() { - return Err(String::from("Warning: No entity kind found amongst the node's types")); - } - - match entity_kind_option.unwrap() { - EntityKind::Interface => self.parse_interface(node, parent_id, model_dict)?, - EntityKind::Telemetry => self.parse_telemetry(node, parent_id, model_dict)?, - EntityKind::Property => self.parse_property(node, parent_id, model_dict)?, - EntityKind::Command => self.parse_command(node, parent_id, model_dict)?, - EntityKind::Relationship => self.parse_relationship(node, parent_id, model_dict)?, - EntityKind::Component => self.parse_component(node, parent_id, model_dict)?, - _ => return Err(String::from("Warning: Unexepcted entity kind found ")), - } - - Ok(()) - } - - /// Parse an interface. - /// - /// # Arguments - /// * `node` - The node that represents an interface. - /// * `parent_id` - The interface's parent id. - /// * `model_dict` - The model dictionary to add the content to. - fn parse_interface( - &mut self, - node: &Node, - parent_id: &Option, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - // @id - required - let mut id: Option = None; - if node.id().is_some() { - id = create_dtmi(node.id().unwrap().as_str()); - } - if id.is_none() { - return Err(format!( - "Interface does not have a valid id '{}'", - node.id().unwrap().as_str() - )); - } - - // contents - optional - for (the_property, the_objects) in node.properties() { - if the_property != "dtmi:dtdl:property:contents;2" { - continue; - } - for the_object in the_objects { - let object: &Object = the_object; - if let Object::Node(node) = object { - self.parse_node(node, &id, model_dict)?; - } - } - } - - // Add the interface to the model dictionary. - let entity_info = Box::new(InterfaceInfoImpl::new( - DTDL_VERSION, - id.clone().unwrap(), - parent_id.clone(), - None, - )); - model_dict.insert(id.clone().unwrap(), entity_info); - - Ok(()) - } - - /// Parse a telemetry. - /// - /// # Arguments - /// * `node` - The node that represents a telemetry. - /// * `parent_id` - The telemetry's parent id. - /// * `model_dict` - The model dictionary to add the content to. - fn parse_telemetry( - &mut self, - node: &Node, - parent_id: &Option, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - // name - optional - let name = self.get_property_value(node, "dtmi:dtdl:property:name;2")?; - - // schema - required - let boxed_schema_info: Box = - self.get_schema(node, model_dict, parent_id)?; - - let mut id: Option = None; - if node.id().is_some() { - id = create_dtmi(node.id().unwrap().as_str()); - } - if id.is_none() { - if name.is_none() { - return Err(String::from( - "We cannot generate an id for the telemtry when we do not have a name.", - )); - } - id = self.generate_id(parent_id, &name.clone().unwrap()); - if id.is_none() { - return Err(String::from("We were not able to generate an id for the telemetry.")); - } - } - - let mut undefined_property_values = HashMap::::new(); - Self::gather_undefined_properties(node, &mut undefined_property_values); - - let mut telemetry_info = TelemetryInfoImpl::new( - DTDL_VERSION, - id.clone().unwrap(), - parent_id.clone(), - None, - name, - Some(boxed_schema_info), - ); - - for (key, value) in undefined_property_values { - telemetry_info.add_undefined_property(key, value); - } - - model_dict.insert(id.unwrap(), Box::new(telemetry_info)); - - Ok(()) - } - - /// Parse a property. - /// - /// # Arguments - /// * `node` - The node that represents a property. - /// * `parent_id` - The property's parent id. - /// * `model_dict` - The model dictionary to add the content to. - fn parse_property( - &mut self, - node: &Node, - parent_id: &Option, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - // name - optional - let name = self.get_property_value(node, "dtmi:dtdl:property:name;2")?; - - // schema - required - let boxed_schema_info: Box = - self.get_schema(node, model_dict, parent_id)?; - - let mut id: Option = None; - if node.id().is_some() { - id = create_dtmi(node.id().unwrap().as_str()); - } - if id.is_none() { - if name.is_none() { - return Err(String::from( - "We cannot generate an id for the property when we do not have a name.", - )); - } - id = self.generate_id(parent_id, &name.clone().unwrap()); - if id.is_none() { - return Err(String::from("We were not able to generate an id for the property.")); - } - } - - let mut undefined_property_values = HashMap::::new(); - Self::gather_undefined_properties(node, &mut undefined_property_values); - - let mut property_info = PropertyInfoImpl::new( - DTDL_VERSION, - id.clone().unwrap(), - parent_id.clone(), - None, - name, - Some(boxed_schema_info), - false, - ); - - for (key, value) in undefined_property_values { - property_info.add_undefined_property(key, value); - } - - model_dict.insert(id.unwrap(), Box::new(property_info)); - - Ok(()) - } - - /// Parse a command. - /// - /// # Arguments - /// * `node` - The node that represents a command. - /// * `parent_id` - The command's parent id. - /// * `model_dict` - The model dictionary to add the content to. - fn parse_command( - &mut self, - node: &Node, - parent_id: &Option, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - // name - optional - let name = self.get_property_value(node, "dtmi:dtdl:property:name;2")?; - - let mut id: Option = None; - if node.id().is_some() { - id = create_dtmi(node.id().unwrap().as_str()); - } - if id.is_none() { - if name.is_none() { - return Err(String::from( - "We cannot generate an id for the command when we do not have a name.", - )); - } - id = self.generate_id(parent_id, &name.clone().unwrap()); - if id.is_none() { - return Err(String::from("We were not able to generate an id for the command.")); - } - } - - let request_payload: Option> = - self.get_payload(node, model_dict, "dtmi:dtdl:property:request;2", &id)?; - let response_payload: Option> = - self.get_payload(node, model_dict, "dtmi:dtdl:property:response;2", &id)?; - - let mut undefined_property_values = HashMap::::new(); - Self::gather_undefined_properties(node, &mut undefined_property_values); - - let mut command_info = CommandInfoImpl::new( - DTDL_VERSION, - id.clone().unwrap(), - parent_id.clone(), - None, - name, - request_payload, - response_payload, - ); - - for (key, value) in undefined_property_values { - command_info.add_undefined_property(key, value); - } - - model_dict.insert(id.clone().unwrap(), Box::new(command_info)); - - Ok(()) - } - - /// Parse a relationship. - /// - /// # Arguments - /// * `node` - The node that represents a relationship. - /// * `parent_id` - The relationship's parent id. - /// * `model_dict` - The model dictionary to add the content to. - fn parse_relationship( - &mut self, - node: &Node, - parent_id: &Option, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - // name - optional - let name = self.get_property_value(node, "dtmi:dtdl:property:name;2")?; - - let mut id: Option = None; - if node.id().is_some() { - id = create_dtmi(node.id().unwrap().as_str()); - } - if id.is_none() { - if name.is_none() { - return Err(String::from( - "We cannot generate an id for the relationship when we do not have a name.", - )); - } - id = self.generate_id(parent_id, &name.clone().unwrap()); - if id.is_none() { - return Err(String::from( - "We were not able to generate an id for the relationship.", - )); - } - } - - let entity_info = Box::new(RelationshipInfoImpl::new( - DTDL_VERSION, - id.clone().unwrap(), - parent_id.clone(), - None, - name, - None, - false, - )); - model_dict.insert(id.unwrap(), entity_info); - - Ok(()) - } - - // Parse a component. - /// - /// # Arguments - /// * `node` - The node that represents a component. - /// * `parent_id` - The component's parent id. - /// * `model_dict` - The model dictionary to add the content to. - fn parse_component( - &mut self, - node: &Node, - parent_id: &Option, - model_dict: &mut ModelDict, - ) -> Result<(), String> { - // name - optional - let name = self.get_property_value(node, "dtmi:dtdl:property:name;2")?; - - // schema - required (note: here the schema property represents an interface) - let schema = self.get_property_value(node, "dtmi:dtdl:property:schema;2")?; - if schema.is_none() { - return Err(String::from("Component does not have a schema property.")); - } - let boxed_interface_info: Box = - self.retrieve_interface_info_from_model_dict(&schema.unwrap(), model_dict)?; - - let mut id: Option = None; - if node.id().is_some() { - id = create_dtmi(node.id().unwrap().as_str()); - } - if id.is_none() { - if name.is_none() { - return Err(String::from( - "We cannot generate an id for the component when we do not have a name.", - )); - } - id = self.generate_id(parent_id, &name.clone().unwrap()); - if id.is_none() { - return Err(String::from("We were not able to generate an id for the component.")); - } - } - - let entity_info = Box::new(ComponentInfoImpl::new( - DTDL_VERSION, - id.clone().unwrap(), - parent_id.clone(), - None, - name, - Some(boxed_interface_info), - )); - model_dict.insert(id.unwrap(), entity_info); - - Ok(()) - } - - /// Find the full path given a relative path and a preset DTDL_PATH environment variable (containing a semicolon-separated list of DTDL directories). - /// - /// # Arguments - /// `relative_path` - The relative path. - pub fn find_full_path(relative_path: &str) -> Result { - match env::var(Self::DTDL_PATH) { - Ok(paths) => { - let split = paths.split(';'); - let vec: Vec<&str> = split.collect(); - for path in vec { - let full_path = Path::new(path).join(relative_path); - if full_path.exists() { - return Ok(full_path.to_str().unwrap().to_string()); - } - } - } - Err(_) => { - return Err(String::from( - "Unable to get the environment variable DTDL_PATH. Please set it.", - )) - } - } - Err(String::from("Unable to resolve the full path")) - } -} - -#[cfg(test)] -mod model_parser_tests { - use super::*; - use log::trace; - use std::fs; - use std::path::Path; - use std::vec::Vec; - - /// The DTDL-path environment variable name. - const CARGO_MANIFEST_DIR: &str = "CARGO_MANIFEST_DIR"; - - /// Retrieve the contents of the DTDL from the specified file path. - /// - /// # Arguments: - /// `file_path` - The file path where the DTDL is located. - fn retrieve_dtdl(file_path: &str) -> Result { - let path = Path::new(file_path); - let read_result = fs::read_to_string(path); - match read_result { - Ok(contents) => Ok(contents), - Err(error) => Err(format!("Unable to retrieve the DTDL due to: {error}")), - } - } - - /// Get the repository's directory. - fn get_repo_dir() -> Option { - // CARGO_MANIFEST_DIR - The directory containing the manifest of your package. - let cargo_manifest_dir_result = env::var(CARGO_MANIFEST_DIR); - if let Ok(cargo_manifest_dir) = cargo_manifest_dir_result { - let cargo_manifest_dir_path = Path::new(&cargo_manifest_dir); - let parent_result = cargo_manifest_dir_path.parent(); - if let Some(parent) = parent_result { - parent.to_str().map(String::from) - } else { - None - } - } else { - None - } - } - - /// Set the DTDL_PATH environment, so that the tests can use it. - fn set_dtdl_path() { - let repo_dir_result = get_repo_dir(); - if let Some(repo_dir) = repo_dir_result { - let value = format!( - "{repo_dir}/external/opendigitaltwins-dtdl/DTDL;{repo_dir}/external/iot-plugandplay-models;{repo_dir}/dtdl-parser/dtdl;{repo_dir}/digital-twin-model/dtdl" - ); - env::set_var(ModelParser::DTDL_PATH, &value); - trace!("{}={value}", ModelParser::DTDL_PATH); - } else { - warn!( - "Unable to set {}, as repo directory could not be determined.", - ModelParser::DTDL_PATH - ); - } - } - - #[test] - fn validation_test() { - set_dtdl_path(); - - let mut json_texts = Vec::::new(); - - let device_information_full_path_result = - ModelParser::find_full_path("dtmi/azure/devicemanagement/deviceinformation-1.json"); - assert!(device_information_full_path_result.is_ok()); - let device_information_contents_result = - retrieve_dtdl(&device_information_full_path_result.unwrap()); - assert!(device_information_contents_result.is_ok()); - json_texts.push(device_information_contents_result.unwrap()); - - let thermostat_full_path_result = ModelParser::find_full_path("v2/samples/Thermostat.json"); - assert!(thermostat_full_path_result.is_ok()); - let thermostat_contents_result = retrieve_dtdl(&thermostat_full_path_result.unwrap()); - assert!(thermostat_contents_result.is_ok()); - json_texts.push(thermostat_contents_result.unwrap()); - - let temp_controller_full_path_result = - ModelParser::find_full_path("v2/samples/TemperatureController.json"); - assert!(temp_controller_full_path_result.is_ok()); - let temp_controller_contents_result = - retrieve_dtdl(&temp_controller_full_path_result.unwrap()); - assert!(temp_controller_contents_result.is_ok()); - json_texts.push(temp_controller_contents_result.unwrap()); - - let mut parser = ModelParser::new(); - let model_dict_result = parser.parse(&json_texts); - assert!( - model_dict_result.is_ok(), - "parse failed due to: {}", - model_dict_result.err().unwrap() - ); - let model_dict = model_dict_result.unwrap(); - assert_eq!( - model_dict.len(), - 31, - "expected length was 31, actual length is {}", - model_dict.len() - ); - } - - #[test] - fn find_full_path_test() { - set_dtdl_path(); - - let find_full_path_result = ModelParser::find_full_path("v3/spec/sdv/hvac.json"); - assert!(find_full_path_result.is_ok()); - let full_path = find_full_path_result.unwrap(); - assert!(full_path.ends_with("/v3/spec/sdv/hvac.json")); - } -} diff --git a/dtdl-parser/src/named_entity_info.rs b/dtdl-parser/src/named_entity_info.rs deleted file mode 100644 index 29d4c273..00000000 --- a/dtdl-parser/src/named_entity_info.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::entity_info::EntityInfo; - -/// An abstract trait that represents named entites. -pub trait NamedEntityInfo: EntityInfo { - /// Returns the name. - fn name(&self) -> &Option; -} diff --git a/dtdl-parser/src/object_info.rs b/dtdl-parser/src/object_info.rs deleted file mode 100644 index 26910bf8..00000000 --- a/dtdl-parser/src/object_info.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::complex_schema_info::ComplexSchemaInfo; -use crate::field_info::FieldInfo; - -/// An object specifies a value compromised of named fields. -pub trait ObjectInfo: ComplexSchemaInfo { - /// Returns the fields. - fn fields(&self) -> &Option>>; -} diff --git a/dtdl-parser/src/object_info_impl.rs b/dtdl-parser/src/object_info_impl.rs deleted file mode 100644 index f2fa6ec5..00000000 --- a/dtdl-parser/src/object_info_impl.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::complex_schema_info::ComplexSchemaInfo; -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::field_info::FieldInfo; -use crate::object_info::ObjectInfo; -use crate::schema_info::SchemaInfo; - -pub struct ObjectInfoImpl { - // EntitytInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // ObjectInfo - fields: Option>>, -} - -impl ObjectInfoImpl { - /// Returns a new ObjectInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version of used to define the object. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this object is defined. - /// * `defined_in` - The identifier of the partition in which this object is defined. - /// * `fields` - The fields. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - fields: Option>>, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - fields, - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for ObjectInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::Object - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl SchemaInfo for ObjectInfoImpl {} - -impl ComplexSchemaInfo for ObjectInfoImpl {} - -impl ObjectInfo for ObjectInfoImpl { - // Returns the fields. - fn fields(&self) -> &Option>> { - &self.fields - } -} - -#[cfg(test)] -mod object_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use serde_json; - - #[test] - fn new_object_info_impl_test() -> Result<(), String> { - let id_result: Option = create_dtmi("dtmi:com:example:Object;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:Cabin;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let fields = Vec::new(); - - let mut object_info = ObjectInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(fields), - ); - object_info.add_undefined_property(String::from("first"), first_propery_value.clone()); - object_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(object_info.dtdl_version(), 2); - assert_eq!(object_info.id(), &id); - assert!(object_info.child_of().is_some()); - assert_eq!(object_info.child_of().clone().unwrap(), child_of); - assert!(object_info.defined_in().is_some()); - assert_eq!(object_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(object_info.entity_kind(), EntityKind::Object); - assert_eq!(object_info.undefined_properties().len(), 2); - assert_eq!( - object_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - object_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match object_info.fields() { - Some(fields) => assert_eq!(fields.len(), 0), - None => return Err(String::from("fields has not been set")), - } - - Ok(()) - } -} diff --git a/dtdl-parser/src/primitive_schema_info.rs b/dtdl-parser/src/primitive_schema_info.rs deleted file mode 100644 index 57f3ea04..00000000 --- a/dtdl-parser/src/primitive_schema_info.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::schema_info::SchemaInfo; - -/// A primitive schema is the trait that represents all primitive schemas, like boolean, integer, string and time. -pub trait PrimitiveSchemaInfo: SchemaInfo {} diff --git a/dtdl-parser/src/primitive_schema_info_impl.rs b/dtdl-parser/src/primitive_schema_info_impl.rs deleted file mode 100644 index 6fae4032..00000000 --- a/dtdl-parser/src/primitive_schema_info_impl.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::primitive_schema_info::PrimitiveSchemaInfo; -use crate::schema_info::SchemaInfo; - -#[derive(Clone, PartialEq, Eq)] -pub struct PrimitiveSchemaInfoImpl { - // EntitytInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - entity_kind: EntityKind, - description: Option, - display_name: Option, - undefined_properties: HashMap, -} - -impl PrimitiveSchemaInfoImpl { - /// Returns a new PrimitiveSchemaInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the primitive schema. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this primitive schema is defined. - /// * `defined_in` - The identifier of the partition in which this relationship is defined. - /// * `entity_kind` - The entity kind. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - entity_kind: EntityKind, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - entity_kind, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for PrimitiveSchemaInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - self.entity_kind - } - - /// Returns the parent identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl SchemaInfo for PrimitiveSchemaInfoImpl {} - -impl PrimitiveSchemaInfo for PrimitiveSchemaInfoImpl {} - -#[cfg(test)] -mod primitive_schema_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use serde_json; - - #[test] - fn new_primitive_schema_info_impl_test() { - let id_result: Option = create_dtmi("dtmi:com:example:String;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let mut primitive_schema_info = - PrimitiveSchemaInfoImpl::new(DTDL_VERSION, id.clone(), None, None, EntityKind::String); - primitive_schema_info - .add_undefined_property(String::from("first"), first_propery_value.clone()); - primitive_schema_info - .add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(primitive_schema_info.dtdl_version(), 2); - assert_eq!(primitive_schema_info.id(), &id); - assert!(primitive_schema_info.child_of().is_none()); - assert!(primitive_schema_info.defined_in().is_none()); - assert!(primitive_schema_info.entity_kind() == EntityKind::String); - assert_eq!(primitive_schema_info.undefined_properties().len(), 2); - assert_eq!( - primitive_schema_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - primitive_schema_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - } -} diff --git a/dtdl-parser/src/primitive_schema_kinds.rs b/dtdl-parser/src/primitive_schema_kinds.rs deleted file mode 100644 index b7eea51c..00000000 --- a/dtdl-parser/src/primitive_schema_kinds.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::entity_kind::EntityKind; - -/// Does the entity kind represent a primitive schema? -/// -/// # Arguments -/// * `entity_kind` - The entity kind. -pub fn is_primitive_schema_kind(entity_kind: EntityKind) -> bool { - entity_kind == EntityKind::Boolean - || entity_kind == EntityKind::Date - || entity_kind == EntityKind::DateTime - || entity_kind == EntityKind::Double - || entity_kind == EntityKind::Duration - || entity_kind == EntityKind::Float - || entity_kind == EntityKind::Integer - || entity_kind == EntityKind::Long - || entity_kind == EntityKind::String - || entity_kind == EntityKind::Time -} diff --git a/dtdl-parser/src/property_info.rs b/dtdl-parser/src/property_info.rs deleted file mode 100644 index 0a77d1c7..00000000 --- a/dtdl-parser/src/property_info.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::content_info::ContentInfo; -use crate::schema_info::SchemaInfo; - -/// A property specifies a value that may be read and in some cases also written. -pub trait PropertyInfo: ContentInfo { - /// Returns the schema. - fn schema(&self) -> &Option>; - - /// Returns whether the property is writable. - fn writable(&self) -> bool; -} diff --git a/dtdl-parser/src/property_info_impl.rs b/dtdl-parser/src/property_info_impl.rs deleted file mode 100644 index 26140866..00000000 --- a/dtdl-parser/src/property_info_impl.rs +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::content_info::ContentInfo; -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::named_entity_info::NamedEntityInfo; -use crate::property_info::PropertyInfo; -use crate::schema_info::SchemaInfo; - -pub struct PropertyInfoImpl { - // EntitytInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // NamedEntityInfo - name: Option, - - // PropertyInfo - schema: Option>, - writable: bool, -} - -impl PropertyInfoImpl { - /// Returns a new PropertyInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the property. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this property is defined. - /// * `defined_in` - The identifier of the partition in which this property is defined. - /// * `schema` - The schema. - /// * `writable` - Is the property writable? - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - name: Option, - schema: Option>, - writable: bool, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - name, - schema, - writable, - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for PropertyInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity/ - fn entity_kind(&self) -> EntityKind { - EntityKind::Property - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl NamedEntityInfo for PropertyInfoImpl { - /// Returns the name of the property. - fn name(&self) -> &Option { - &self.name - } -} - -impl ContentInfo for PropertyInfoImpl {} - -impl PropertyInfo for PropertyInfoImpl { - /// Returns the schema. - fn schema(&self) -> &Option> { - &self.schema - } - - /// Returns whether the property is writable. - fn writable(&self) -> bool { - self.writable - } -} - -#[cfg(test)] -mod property_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use crate::primitive_schema_info_impl::PrimitiveSchemaInfoImpl; - use serde_json; - - #[test] - fn new_property_info_impl_test() -> Result<(), String> { - let id_result: Option = create_dtmi("dtmi:com:example:Thermostat;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:Cabin;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let schema_info_id: Option = create_dtmi("dtmi:dtdl:class:String;2"); - assert!(schema_info_id.is_some()); - - let boxed_schema_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - schema_info_id.unwrap(), - None, - None, - EntityKind::String, - )); - - let mut property_info = PropertyInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(String::from("one")), - Some(boxed_schema_info), - true, - ); - property_info.add_undefined_property(String::from("first"), first_propery_value.clone()); - property_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(property_info.dtdl_version(), 2); - assert_eq!(property_info.id(), &id); - assert!(property_info.child_of().is_some()); - assert_eq!(property_info.child_of().clone().unwrap(), child_of); - assert!(property_info.defined_in().is_some()); - assert_eq!(property_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(property_info.entity_kind(), EntityKind::Property); - assert_eq!(property_info.undefined_properties().len(), 2); - assert_eq!( - property_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - property_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match property_info.name() { - Some(name) => assert_eq!(name, "one"), - None => return Err(String::from("name has not been set")), - } - - match property_info.schema() { - Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::String), - None => return Err(String::from("schema has not been set")), - } - - assert!(property_info.writable()); - - Ok(()) - } -} diff --git a/dtdl-parser/src/relationship_info.rs b/dtdl-parser/src/relationship_info.rs deleted file mode 100644 index a5e53b53..00000000 --- a/dtdl-parser/src/relationship_info.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::content_info::ContentInfo; -use crate::schema_info::SchemaInfo; - -/// A relationshp specifies an assoication with an interface. It allows graphs to be built. -pub trait RelationshipInfo: ContentInfo { - /// Returns the schema. - fn schema(&self) -> &Option>; - - /// Returns whether the property is writable. - fn writable(&self) -> bool; -} diff --git a/dtdl-parser/src/relationship_info_impl.rs b/dtdl-parser/src/relationship_info_impl.rs deleted file mode 100644 index caf4f4bc..00000000 --- a/dtdl-parser/src/relationship_info_impl.rs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::content_info::ContentInfo; -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::named_entity_info::NamedEntityInfo; -use crate::relationship_info::RelationshipInfo; -use crate::schema_info::SchemaInfo; - -pub struct RelationshipInfoImpl { - // EntitytInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // NamedEntityInfo - name: Option, - - // RelationshipInfo - schema: Option>, - writable: bool, -} - -impl RelationshipInfoImpl { - /// Returns a new RelationshipInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the relationship. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this relationship is defined. - /// * `defined_in` - The identifier of the partition in which this relationship is defined. - /// * `name` - The name. - /// * `schema` - The schema. - /// * `writable` - Is the relationship writable? - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - name: Option, - schema: Option>, - writable: bool, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - name, - schema, - writable, - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for RelationshipInfoImpl { - /// Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::Property - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl NamedEntityInfo for RelationshipInfoImpl { - /// Returns the name. - fn name(&self) -> &Option { - &self.name - } -} - -impl ContentInfo for RelationshipInfoImpl {} - -impl RelationshipInfo for RelationshipInfoImpl { - /// Returns the schema. - fn schema(&self) -> &Option> { - &self.schema - } - - /// Returns whether the relationship is writable. - fn writable(&self) -> bool { - self.writable - } -} - -#[cfg(test)] -mod relationship_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use crate::primitive_schema_info_impl::PrimitiveSchemaInfoImpl; - use serde_json; - - #[test] - fn new_relationship_info_impl_test() -> Result<(), String> { - let id_result: Option = create_dtmi("dtmi:com:example:my_relationship;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:vehicle;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let schema_info_id: Option = create_dtmi("dtmi:dtdl:class:String;2"); - assert!(schema_info_id.is_some()); - - let boxed_schema_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - schema_info_id.unwrap(), - None, - None, - EntityKind::String, - )); - - let mut relationship_info = RelationshipInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(String::from("one")), - Some(boxed_schema_info), - false, - ); - relationship_info - .add_undefined_property(String::from("first"), first_propery_value.clone()); - relationship_info - .add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(relationship_info.dtdl_version(), 2); - assert_eq!(relationship_info.id(), &id); - assert!(relationship_info.child_of().is_some()); - assert_eq!(relationship_info.child_of().clone().unwrap(), child_of); - assert!(relationship_info.defined_in().is_some()); - assert_eq!(relationship_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(relationship_info.entity_kind(), EntityKind::Property); - assert_eq!(relationship_info.undefined_properties().len(), 2); - assert_eq!( - relationship_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - relationship_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match relationship_info.name() { - Some(name) => assert_eq!(name, "one"), - None => return Err(String::from("name has not been set")), - } - - Ok(()) - } -} diff --git a/dtdl-parser/src/schema_field_info.rs b/dtdl-parser/src/schema_field_info.rs deleted file mode 100644 index 119a2df1..00000000 --- a/dtdl-parser/src/schema_field_info.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::named_entity_info::NamedEntityInfo; -use crate::schema_info::SchemaInfo; - -/// An abstract trait that represents an entity that has a schema field. -pub trait SchemaFieldInfo: NamedEntityInfo { - /// Returns the schema. - fn schema(&self) -> &Option>; -} diff --git a/dtdl-parser/src/schema_info.rs b/dtdl-parser/src/schema_info.rs deleted file mode 100644 index 12c65115..00000000 --- a/dtdl-parser/src/schema_info.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::entity_info::EntityInfo; - -/// A schema is the base trait for all primitive and complex schemas. -pub trait SchemaInfo: EntityInfo {} diff --git a/dtdl-parser/src/telemetry_info.rs b/dtdl-parser/src/telemetry_info.rs deleted file mode 100644 index 9f8553dc..00000000 --- a/dtdl-parser/src/telemetry_info.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use crate::content_info::ContentInfo; -use crate::schema_info::SchemaInfo; - -/// A telemetry specifies data that is emitted as a stream. -pub trait TelemetryInfo: ContentInfo { - /// Returns the schema. - fn schema(&self) -> &Option>; -} diff --git a/dtdl-parser/src/telemetry_info_impl.rs b/dtdl-parser/src/telemetry_info_impl.rs deleted file mode 100644 index 5f4c7f49..00000000 --- a/dtdl-parser/src/telemetry_info_impl.rs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde_json::Value; -use std::any::Any; -use std::collections::HashMap; - -use crate::content_info::ContentInfo; -use crate::dtmi::Dtmi; -use crate::entity_info::EntityInfo; -use crate::entity_kind::EntityKind; -use crate::named_entity_info::NamedEntityInfo; -use crate::schema_info::SchemaInfo; -use crate::telemetry_info::TelemetryInfo; - -pub struct TelemetryInfoImpl { - // EntityInfo - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - description: Option, - display_name: Option, - undefined_properties: HashMap, - - // NamedEntityInfo - name: Option, - - // TelemetryInfo - schema: Option>, -} - -impl TelemetryInfoImpl { - /// Returns a new TelemetryInfoImpl. - /// - /// # Arguments - /// * `dtdl_version` - The DTDL version used to define the telemetry. - /// * `id` - The identifier. - /// * `child_of` - The identifier of the parent element in which this telemetry is defined. - /// * `defined_in` - The identifier of the partition in which this telemetry is defined. - /// * `name` - The name. - /// * `schema` - The schema. - pub fn new( - dtdl_version: i32, - id: Dtmi, - child_of: Option, - defined_in: Option, - name: Option, - schema: Option>, - ) -> Self { - Self { - dtdl_version, - id, - child_of, - defined_in, - description: None, - display_name: None, - undefined_properties: HashMap::::new(), - name, - schema, - } - } - - /// Add an undefined property. - /// # Arguments - /// * `key` - The property's name. - /// * `value` - The property's value. - pub fn add_undefined_property(&mut self, key: String, value: Value) { - self.undefined_properties.insert(key, value); - } -} - -impl EntityInfo for TelemetryInfoImpl { - // Returns the DTDL version. - fn dtdl_version(&self) -> i32 { - self.dtdl_version - } - - /// Returns the identifier. - fn id(&self) -> &Dtmi { - &self.id - } - - /// Returns the kind of entity. - fn entity_kind(&self) -> EntityKind { - EntityKind::Telemetry - } - - /// Returns the parent's identifier. - fn child_of(&self) -> &Option { - &self.child_of - } - - /// Returns the enclosing partition's identifier. - fn defined_in(&self) -> &Option { - &self.defined_in - } - - // Returns the description for this entity. - fn description(&self) -> &Option { - &self.description - } - - // Returns the display name for this entity. - fn display_name(&self) -> &Option { - &self.display_name - } - - /// Returns all undefined properties. - fn undefined_properties(&self) -> &HashMap { - &self.undefined_properties - } - - /// Returns the instance as an Any. - fn as_any(&self) -> &dyn Any { - self - } -} - -impl NamedEntityInfo for TelemetryInfoImpl { - /// Returns the name. - fn name(&self) -> &Option { - &self.name - } -} - -impl ContentInfo for TelemetryInfoImpl {} - -impl TelemetryInfo for TelemetryInfoImpl { - /// Returns the schema. - fn schema(&self) -> &Option> { - &self.schema - } -} - -#[cfg(test)] -mod telemetry_info_impl_tests { - use super::*; - use crate::dtmi::{create_dtmi, Dtmi}; - use crate::model_parser::DTDL_VERSION; - use crate::primitive_schema_info_impl::PrimitiveSchemaInfoImpl; - use serde_json; - - #[test] - fn new_telemetry_info_impl_test() -> Result<(), String> { - let id_result: Option = create_dtmi("dtmi:com:example:Thermostat;1.0"); - assert!(id_result.is_some()); - let id = id_result.unwrap(); - - let child_of_result: Option = create_dtmi("dtmi:com:example:Cabin;1.0"); - assert!(child_of_result.is_some()); - let child_of = child_of_result.unwrap(); - - let defined_in_result: Option = create_dtmi("dtmi:com:example;1.0"); - assert!(defined_in_result.is_some()); - let defined_in = defined_in_result.unwrap(); - - let first_propery_value: Value = serde_json::from_str("{\"first\": \"this\"}").unwrap(); - let second_propery_value: Value = serde_json::from_str("{\"second\": \"that\"}").unwrap(); - - let schema_info_id: Option = create_dtmi("dtmi:dtdl:class:String;2"); - assert!(schema_info_id.is_some()); - - let boxed_schema_info = Box::new(PrimitiveSchemaInfoImpl::new( - DTDL_VERSION, - schema_info_id.unwrap(), - None, - None, - EntityKind::String, - )); - - let mut telemetry_info = TelemetryInfoImpl::new( - DTDL_VERSION, - id.clone(), - Some(child_of.clone()), - Some(defined_in.clone()), - Some(String::from("one")), - Some(boxed_schema_info), - ); - telemetry_info.add_undefined_property(String::from("first"), first_propery_value.clone()); - telemetry_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - - assert_eq!(telemetry_info.dtdl_version(), 2); - assert_eq!(telemetry_info.id(), &id); - assert!(telemetry_info.child_of().is_some()); - assert_eq!(telemetry_info.child_of().clone().unwrap(), child_of); - assert!(telemetry_info.defined_in().is_some()); - assert_eq!(telemetry_info.defined_in().clone().unwrap(), defined_in); - assert_eq!(telemetry_info.entity_kind(), EntityKind::Telemetry); - assert_eq!(telemetry_info.undefined_properties().len(), 2); - assert_eq!( - telemetry_info.undefined_properties().get("first").unwrap().clone(), - first_propery_value - ); - assert_eq!( - telemetry_info.undefined_properties().get("second").unwrap().clone(), - second_propery_value - ); - - match telemetry_info.name() { - Some(name) => assert_eq!(name, "one"), - None => return Err(String::from("name has not been set")), - } - - match telemetry_info.schema() { - Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::String), - None => return Err(String::from("schema has not been set")), - } - - Ok(()) - } -} diff --git a/dtdl-tools/Cargo.toml b/dtdl-tools/Cargo.toml new file mode 100644 index 00000000..75435e1f --- /dev/null +++ b/dtdl-tools/Cargo.toml @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +[package] +name = "dtdl-tools" +version = "0.1.0" +edition = "2021" +license = "MIT" diff --git a/dtdl-tools/README.md b/dtdl-tools/README.md new file mode 100644 index 00000000..367aafde --- /dev/null +++ b/dtdl-tools/README.md @@ -0,0 +1,23 @@ +# DTDL Tools + +DTDL is fundamental to Ibeji. These tools will help developers to use DTDL to build their own in-vehicle digital twin model. + +## DTDL Validator + +The DTDL Validator is an application that validates DTDL files. It uses the .NET DTDLParser. + +The DTDL Validator application is built by Cargo. It can be found here: ibeji/target/debug/dtdl-validator/bin/Debug/net7.0/dtdl-validator. +It takes two command line arguments: + +* -d {directory name} The directory that contains the DTDL files. +* -e {file extension} The file extension used by the DTDL files. The default is "json". + +The CI/CD pipeline automatically validates DTDL files found under the ibeji/digital-twin-model/dtdl directory via dtdl-tools +test suite. Additonal directories containing DTDL files can also be checked by addining new test cases based on the one for +the ibeji/digital-twin-model/dtdl directory. + +If you wish to manually run the the DTDL Validator application, then install it from Cargo's out directory to a custom directory by +using the ibeji/dtdl-tools/scripts/install-dtdl-validator.sh script. This script takes two command line arguments: + +* -s {source directory} The Cargo out directory where the application was built. The default is the current directory. +* -d {destination directory} The custom directory where you want the application installed. The default is "$HOME/.cargo/bin/dtdl-validator". diff --git a/dtdl-tools/build.rs b/dtdl-tools/build.rs new file mode 100644 index 00000000..66ff3307 --- /dev/null +++ b/dtdl-tools/build.rs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use std::env; +use std::io::{self, Write}; +use std::process::Command; + +// This build script builds all the .NET projects in this dtdl-tools folder. +// Running 'cargo build' will build the .NET projects and the Rust crates. +fn main() { + const DOTNET_COMMAND: &str = "dotnet"; + const DOTNET_BUILD_ARG: &str = "build"; + const CSPROJ_PATHS: &[&str] = &["src/dtdl-validator/dtdl-validator.csproj"]; + const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); + + for csproj in CSPROJ_PATHS { + let output = + Command::new(DOTNET_COMMAND).arg(DOTNET_BUILD_ARG).arg(csproj).output().unwrap(); + + if !output.status.success() { + panic!("Failed to run due to {output:?}"); + } + + io::stdout().write_all(&output.stdout).unwrap(); + } + + println!("cargo:rerun-if-changed={CARGO_MANIFEST_DIR}/src/dtdl-validator"); +} diff --git a/dtdl-tools/scripts/install-dtdl-validator.sh b/dtdl-tools/scripts/install-dtdl-validator.sh new file mode 100755 index 00000000..8d622e4c --- /dev/null +++ b/dtdl-tools/scripts/install-dtdl-validator.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +set -e + +# Default values +src_dir="." +dst_dir="$HOME/.cargo/bin/dtdl-validator" + +# Parse command line options +while getopts "s:d:" opt; do + case ${opt} in + s) + src_dir="$OPTARG" + ;; + d) + dst_dir="$OPTARG" + ;; + \?) + echo "Invalid option: -$OPTARG" 1>&2 + echo "Usage: $0 [-s source_directory] [-d destination_directory]" 1>&2 + exit 1 + ;; + esac +done + +# Check if dtdl-validator exists in the source directory +if [[ ! -f "$src_dir/dtdl-validator" ]]; then + echo "Error: dtdl-validator must exist in the source directory." 1>&2 + exit 1 +fi + +# Create the destination directory if it does not exist +mkdir -p "$dst_dir" + +# Copy dtdl-validator, its config file and all assocaited dll files to the destination directory +cp "$src_dir/dtdl-validator" "$dst_dir" +cp "$src_dir"/dtdl-validator.runtimeconfig.json "$dst_dir" +cp "$src_dir"/*.dll "$dst_dir" + +echo "dtdl-validator has been successfully installed in $dst_dir" + +exit 0 diff --git a/dtdl-tools/src/dtdl-validator/Directory.Build.props b/dtdl-tools/src/dtdl-validator/Directory.Build.props new file mode 100644 index 00000000..4b0e50fc --- /dev/null +++ b/dtdl-tools/src/dtdl-validator/Directory.Build.props @@ -0,0 +1,7 @@ + + + + $(OUT_DIR)/$(BaseOutputPath) + $(OUT_DIR)/$(BaseIntermediateOutputPath) + + diff --git a/dtdl-tools/src/dtdl-validator/DtdlValidator.cs b/dtdl-tools/src/dtdl-validator/DtdlValidator.cs new file mode 100644 index 00000000..19f50d4e --- /dev/null +++ b/dtdl-tools/src/dtdl-validator/DtdlValidator.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +using DTDLParser; +using DTDLParser.Models; +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +class Program +{ + // Exit codes. + private const int EXIT_SUCCESS = 0; + private const int EXIT_FAILURE = 1; + + /// + /// This method validates all of the DTDL files with the provided extension that are located + /// under the provided directory. + /// + /// The directory that contains the DTDL files that we wish to validate. + /// The extension used by the DTDL files. + /// + /// EXIT_SUCCESS when all if the DTDL files are valid. + /// EXIT_FAILURE when any of the DTDL files are NOT valid. + /// + static int ValidateDtdl(DirectoryInfo directory, String extension) + { + if (!directory.Exists) + { + Console.WriteLine($"Directory {directory.FullName} does not exist."); + return EXIT_FAILURE; + } + + var files = Directory.GetFiles(directory.FullName, $"*.{extension}", SearchOption.AllDirectories); + + if (!files.Any()) + { + Console.WriteLine($"No files with extension .{extension} found in directory {directory}"); + return EXIT_FAILURE; + } + + var parser = new ModelParser(); + + bool failureOccured = false; + foreach (var file in files) + { + try + { + var dtdl = File.ReadAllText(file); + parser.ParseAsync(dtdl).GetAwaiter().GetResult(); + Console.WriteLine($"{file} - ok"); + } + catch (ParsingException ex) + { + Console.WriteLine($"{file} - failed"); + foreach (var error in ex.Errors) { + Console.WriteLine($" {error}"); + } + failureOccured = true; + } + } + + if (failureOccured) + { + return EXIT_FAILURE; + } + else + { + return EXIT_SUCCESS; + } + } + + static async Task Main(string[] args) + { + var directoryArgument = + new Argument( + "directory", + description: "The directory that contains the DTDL files."); + var extensionOption = + new Option( + "-e", + getDefaultValue: () => "json", + description: "The file extension used by the DTDL files."); + + int exitCode = EXIT_SUCCESS; + + var cmd = new RootCommand(); + cmd.AddArgument(directoryArgument); + cmd.AddOption(extensionOption); + cmd.SetHandler((directory, extension) => + { + exitCode = ValidateDtdl(directory!, extension!); + }, + directoryArgument, extensionOption); + + await cmd.InvokeAsync(args); + + return exitCode; + } +} diff --git a/dtdl-tools/src/dtdl-validator/dtdl-validator.csproj b/dtdl-tools/src/dtdl-validator/dtdl-validator.csproj new file mode 100644 index 00000000..f7aa0b82 --- /dev/null +++ b/dtdl-tools/src/dtdl-validator/dtdl-validator.csproj @@ -0,0 +1,20 @@ + + + + + Exe + net7.0 + + + + + + + + + + + + + + diff --git a/dtdl-tools/src/lib.rs b/dtdl-tools/src/lib.rs new file mode 100644 index 00000000..3e1d2b90 --- /dev/null +++ b/dtdl-tools/src/lib.rs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +// This lib.rs is for running the .NET unit tests in this dtdl-tools folder. +// Running 'cargo test' will run all the .NET unit tests and the Rust unit tests. +#[cfg(test)] +mod dtdl_tools_tests { + use std::env; + use std::io::{self, Write}; + use std::path::Path; + use std::process::Command; + + // The out directory is the directory that contains the build artifacts. + const OUT_DIR: &str = env!("OUT_DIR"); + + const DTDL_VALIDATOR_FILENAME: &str = "dtdl-validator"; + const DTDL_VALIDATOR_BIN_DIR: &str = "Debug/net7.0"; + const DTDL_VALIDATOR_EXT_OPTION: &str = "-e"; + + /// Validate DTDL files. + /// + /// # Arguments + /// * `directory` - The directory that contains the DTDL files that you wish to validate. + /// * `extension` - The file extension that the DTDL files use. + fn validate_dtdl_files(directory: &str, extension: &str) -> bool { + let dtdl_validator_command_path = + Path::new(OUT_DIR).join(DTDL_VALIDATOR_BIN_DIR).join(DTDL_VALIDATOR_FILENAME); + + println!("dtdl_validator_command_path = '{:?}", dtdl_validator_command_path); + + let dtdl_validator_output = Command::new(dtdl_validator_command_path) + .arg(directory) + .arg(DTDL_VALIDATOR_EXT_OPTION) + .arg(extension) + .output() + .unwrap(); + + if !dtdl_validator_output.status.success() { + io::stdout().write_all(&dtdl_validator_output.stdout).unwrap(); + } + + dtdl_validator_output.status.success() + } + + #[test] + fn validate_digital_twin_model_dtdl_files() { + assert!(validate_dtdl_files("../digital-twin-model/dtdl", "json")); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 68283abf..7eb23c42 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2022-08-11" +channel = "1.72.1" diff --git a/tools/dotnet_append_to_notice.sh b/tools/dotnet_append_to_notice.sh new file mode 100755 index 00000000..47c8eaec --- /dev/null +++ b/tools/dotnet_append_to_notice.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +set -e + +# Check if the correct number of arguments are provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 path_to_markdown_file path_to_json_file" + exit 1 +fi + +# Assign arguments to variables for clarity +markdown_file="$1" +json_file="$2" + +# Check if the markdown file exists +if [ ! -f "$markdown_file" ]; then + echo "Error: markdown file '$markdown_file' not found" + exit 1 +fi + +# Check if the JSON file exists +if [ ! -f "$json_file" ]; then + echo "Error: JSON file '$json_file' not found" + exit 1 +fi + +# Append header to markdown file +echo -e "\n\n# .NET Third Party Licenses\nThe following lists the licenses of the .NET projects used.\n" >> "$markdown_file" + +# Read JSON file and append information to markdown file +while read -r line; do + # Extract values from JSON object + license_type=$(echo "$line" | jq -r '.LicenseType') + package_name=$(echo "$line" | jq -r '.PackageName') + package_version=$(echo "$line" | jq -r '.PackageVersion') + package_url=$(echo "$line" | jq -r '.PackageUrl') + license_description=$(echo "$line" | jq -r '.LicenseDescription') + + # Append information to markdown file in specified format + echo "### $license_type" >> "$markdown_file" + echo -e "\n#### Used by\n" >> "$markdown_file" + echo "- [$package_name]( $package_url ) $package_version" >> "$markdown_file" + echo -e "\n#### License\n" >> "$markdown_file" + echo '```text' >> "$markdown_file" + echo -e "$license_description" >> "$markdown_file" + echo '```' >> "$markdown_file" +done < <(jq -c '.[]' "$json_file") + +echo -e "\n## Disclaimer" >> "$markdown_file" +echo -e " +This .NET Third Party Licenses list has been generated with [nuget-license](https://github.com/tomchavakis/nuget-license), \ +licensed under [Apache License 2.0](https://github.com/tomchavakis/nuget-license/blob/master/LICENSE)" >> "$markdown_file" + +exit 0 diff --git a/tools/dotnet_get_licenses.sh b/tools/dotnet_get_licenses.sh new file mode 100755 index 00000000..b9df1bbb --- /dev/null +++ b/tools/dotnet_get_licenses.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +set -e + +# Check if the correct number of arguments are provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 path_to_json_file path_to_text_files_directory" + exit 1 +fi + +# Assign arguments to variables for clarity +json_file="$1" +text_files_dir="$2" + +# Check if the JSON file exists +if [ ! -f "$json_file" ]; then + echo "Error: JSON file '$json_file' not found" + exit 1 +fi + +# Check if the text files directory exists +if [ ! -d "$text_files_dir" ]; then + echo "Error: text files directory '$text_files_dir' not found" + exit 1 +fi + +# Create a temporary file to store the updated JSON +temp_file=$(mktemp) + +# Read JSON file and update elements with LicenseDescription field +while read -r line; do + # Extract values from JSON object + package_name=$(echo "$line" | jq -r '.PackageName') + package_version=$(echo "$line" | jq -r '.PackageVersion') + + # Construct path to license description text file + license_description_file="${text_files_dir}/${package_name}_${package_version}.txt" + + # Check if the license description text file exists + if [ ! -f "$license_description_file" ]; then + echo "Error: license description text file '$license_description_file' not found" + exit 1 + fi + + # Read license description text file and add LicenseDescription field to JSON object + license_description=$(cat "$license_description_file") + updated_json=$(echo "$line" | jq --arg desc "$license_description" '. + {LicenseDescription: $desc}') + + # Write updated JSON object to temporary file + echo "$updated_json" >> "$temp_file" +done < <(jq -c '.[]' "$json_file") + +# Overwrite original JSON file with updated JSON from temporary file +jq -s '.' "$temp_file" > "$json_file" + +# Remove temporary file +rm "$temp_file" + +exit 0 diff --git a/tools/dotnet_notice_generation.sh b/tools/dotnet_notice_generation.sh new file mode 100755 index 00000000..a1f98875 --- /dev/null +++ b/tools/dotnet_notice_generation.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +set -e + +cd "$(dirname "$0")/.." + +# Check if the correct number of argments are passed +if [ "$#" -lt 3 ] ; then + echo "Usage: $0 " + exit 1 +fi + +# Assign notice_file_path and dotnet_directory to arguments +notice_file_path="$1" +dotnet_directory="$2" +license_url_to_license_mappings="$3" + +# Check if the notice file exists +if [ ! -f "$notice_file_path" ]; then + echo "Error: Notice file '$notice_file_path' not found" + exit 1 +fi + +if ! dotnet tool list --global | grep -q 'dotnet-project-licenses'; then + dotnet tool install --global dotnet-project-licenses +fi + +dotnet_licenses_output_directory="$dotnet_directory/dotnet_licenses_output" +mkdir -p "$dotnet_licenses_output_directory" +echo "Getting the .NET Third Party licenses" + +dotnet-project-licenses -i $dotnet_directory -o -f "$dotnet_licenses_output_directory" -u --json -e -c \ + --licenseurl-to-license-mappings "$license_url_to_license_mappings" + +./tools/dotnet_get_licenses.sh "$dotnet_licenses_output_directory/licenses.json" "$dotnet_directory/dotnet_licenses_output" +./tools/dotnet_append_to_notice.sh "$notice_file_path" "$dotnet_licenses_output_directory/licenses.json" + +rm -r "$dotnet_licenses_output_directory" + +exit 0 \ No newline at end of file diff --git a/tools/notice_generation.sh b/tools/notice_generation.sh index 97cdb56b..bbedc4e1 100755 --- a/tools/notice_generation.sh +++ b/tools/notice_generation.sh @@ -36,6 +36,10 @@ NOTICE_FILENAME="NOTICE" echo "Running cargo-about for NOTICE file generation..." cargo about generate --workspace devops/cg/about.hbs --config devops/cg/about.toml > $NOTICE_FILENAME +DOTNET_SRC_DIRECTORY="dtdl-tools/" +echo "Appending .NET Third Party licenses to $NOTICE_FILENAME" +./tools/dotnet_notice_generation.sh $NOTICE_FILENAME $DOTNET_SRC_DIRECTORY ./devops/cg/license_url_to_type.json + if [ -z "$(git diff --name-only $NOTICE_FILENAME)" ] then echo "File not changed"