diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8e3019e..8ddde93e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,14 +2,14 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - repo: https://github.com/APIDevTools/swagger-cli - rev: v4.0.3 + rev: v4.0.4 hooks: - id: swagger-validation name: Check market-api spec @@ -20,3 +20,6 @@ repos: - id: swagger-validation name: Check payment-api spec files: specs/payment-api.yaml$ + - id: swagger-validation + name: Check network-api spec + files: specs/net-api.yaml$ diff --git a/Cargo.toml b/Cargo.toml index c572fca0..fc990fed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,16 @@ license = "LGPL-3.0" edition = "2021" [features] -default = [] +default = ['provider', 'requestor'] cli = ['structopt'] + sgx = [ 'graphene-sgx', 'lazy_static', 'secp256k1', 'openssl', 'secp256k1/serde', 'secp256k1/rand', - 'ya-client-model/sgx', 'rand'] + 'ya-client-model/sgx', 'rand', 'hex'] + +provider= [] +requestor = [] [workspace] members = [ @@ -23,31 +27,38 @@ members = [ [workspace.dependencies] secp256k1 = ">=0.23,<0.28" +serde = { version = "1.0.146" } +serde_json = "1.0.96" +thiserror = "1.0.40" +rand = "0.8.5" +chrono = { version = "0.4", default-features = false } +openssl = "0.10" +hex = "0.4" [dependencies] -ya-client-model = { version = "^0.5", path = "model" } +ya-client-model = { version = "^0.5", path = "model", features=["debug"] } awc = { version = "3", default-features = false } actix-codec = "0.5" bytes = "1" -chrono = { version = "0.4", default-features = false } +hex = { workspace=true, optional = true} +chrono = { workspace=true, default-features = false } envy = "0.4" futures = "0.3" -hex = "0.4" heck = "0.4.1" log = "0.4" mime = "0.3.17" -serde = "1" -serde_json = "1.0" +serde = { workspace=true } +serde_json = { workspace=true } serde_qs = "0.12" -thiserror = "1.0.40" +thiserror = { workspace=true } url = "2" graphene-sgx = { version = "0.3.3", optional = true } lazy_static = { version = "1.4", optional = true } secp256k1 = { workspace=true, optional = true } -rand = { version = "0.8.5", optional = true } +rand = { workspace=true, optional = true } structopt = { version = "0.3", optional = true } -openssl = { version = "0.10", optional = true } +openssl = { workspace=true, optional = true } [dev-dependencies] actix-rt = "2.7.0" diff --git a/README.md b/README.md index 29824fbf..3a339beb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Yagna public API +# Golem Node REST API [![CI](https://github.com/golemfactory/ya-client/actions/workflows/rust.yml/badge.svg)](https://github.com/golemfactory/ya-client/actions/workflows/rust.yml) Public Yagna REST APIs client binding with Data Model and specifications in [OpenAPI](http://spec.openapis.org/) format. diff --git a/model/Cargo.toml b/model/Cargo.toml index 43dfd192..f8a62375 100644 --- a/model/Cargo.toml +++ b/model/Cargo.toml @@ -8,26 +8,41 @@ repository = "https://github.com/golemfactory/ya-client" license = "LGPL-3.0" edition = "2018" +[[example]] +name = "gen-json-schema" +required-features = ["json-schema"] + [features] -default = [] +default = ['node-id-compat'] +node-id-compat = [] +json-schema = ["schemars", "schemars/chrono", "serde_json/preserve_order"] +server = [] +debug = [] +sgx = ['secp256k1', 'openssl', 'hex', 'secp256k1/serde', 'rand'] with-diesel = ['diesel'] -sgx = ['secp256k1', 'openssl', 'hex', 'secp256k1/serde'] [dependencies] bigdecimal = { version = "0.2", features = ["serde"]} -chrono = { version = "0.4", features = ["serde"]} +chrono = { workspace=true, features = ["serde", "clock"]} derive_more = "0.99" -rand = "0.8" -serde = { version = "1.0.146", features = ["derive"] } -serde_json = "1.0.96" +rand = { workspace=true, optional = true } +serde = { workspace=true, features = ["derive"] } +serde_json = { workspace=true } strum = "0.24.1" strum_macros = "0.24.3" -thiserror = "1.0" +thiserror = { workspace=true } diesel = { version = "1.4", optional = true } hex = { version = "0.4", optional = true} secp256k1 = { workspace = true, optional = true } -openssl = { version = "0.10", optional = true } +openssl = { workspace=true, optional = true } + +schemars = { version = "0.8", optional = true } + +[dev-dependencies] +flexbuffers="2.0.0" +serde_yaml = "0.9.21" +json-schema-diff = "0.1.6" [package.metadata.release] dev-version = false diff --git a/model/examples/gen-json-schema.rs b/model/examples/gen-json-schema.rs new file mode 100644 index 00000000..dd1838d0 --- /dev/null +++ b/model/examples/gen-json-schema.rs @@ -0,0 +1,58 @@ +use schemars::gen::SchemaSettings; +use schemars::schema_for; +use serde_yaml; +use ya_client_model::net::*; + +fn main() { + let mut net_api_schema: serde_json::Value = + serde_yaml::from_str(include_str!("../../specs/net-api.yaml")).unwrap(); + + let objects = net_api_schema + .get_mut("components") + .unwrap() + .get_mut("schemas") + .unwrap() + .as_object_mut() + .unwrap(); + + let settings = SchemaSettings::draft07().with(|s| { + s.option_nullable = true; + s.option_add_null_type = false; + s.inline_subschemas = false; + }); + let mut gen = settings.into_generator(); + + let mut show_diff_for = |name, schema, skip| { + let lhs: serde_json::Value = objects.remove(name).unwrap().clone(); + let rhs: serde_json::Value = serde_json::to_value(schema).unwrap(); + let df = json_schema_diff::diff(lhs, rhs.clone()).unwrap(); + + if df.is_empty() || skip { + eprintln!("{}", serde_yaml::to_string(&rhs).unwrap()); + eprintln!("{} is equal", name); + } else { + eprintln!("# {}", name); + eprintln!("{}", serde_yaml::to_string(&rhs).unwrap()); + eprintln!("# {} changes", name); + for change in df { + eprintln!("{:20} {:?}", change.path, change.change); + } + } + //eprintln!("L: {}", serde_json::to_string_pretty(&lhs).unwrap()); + }; + show_diff_for("Node", gen.root_schema_for::(), false); + show_diff_for("Address", gen.root_schema_for::
(), false); + show_diff_for("Connection", gen.root_schema_for::(), false); + show_diff_for("Network", gen.root_schema_for::(), false); + show_diff_for("Protocol", gen.root_schema_for::(), true); + show_diff_for("Proxy", gen.root_schema_for::(), false); + + for key in objects.keys() { + eprintln!( + "show_diff_for(\"{}\", gen.root_schema_for::<{}>(), false);", + key, key + ); + } + + assert!(objects.is_empty()) +} diff --git a/model/src/activity.rs b/model/src/activity.rs index 2c1a2ae0..5ccce6b8 100644 --- a/model/src/activity.rs +++ b/model/src/activity.rs @@ -1,15 +1,20 @@ -pub mod activity_state; -pub mod activity_usage; +//! The Activity API. + +mod activity_state; +mod activity_usage; mod create_activity; + #[cfg(feature = "sgx")] +#[doc(hidden)] pub mod encrypted; -pub mod exe_script_command; -pub mod exe_script_command_result; -pub mod exe_script_command_state; -pub mod exe_script_request; -pub mod provider_event; -pub mod runtime_event; +mod exe_script_command; +mod exe_script_command_result; +mod exe_script_command_state; +mod exe_script_request; +mod provider_event; +mod runtime_event; #[cfg(feature = "sgx")] +#[doc(hidden)] mod sgx_credentials; pub use self::activity_state::{ActivityState, State, StatePair}; @@ -20,9 +25,10 @@ pub use self::exe_script_command::{ExeScriptCommand, FileSet, SetEntry, SetObjec pub use self::exe_script_command_result::{CommandOutput, CommandResult, ExeScriptCommandResult}; pub use self::exe_script_command_state::ExeScriptCommandState; pub use self::exe_script_request::ExeScriptRequest; -pub use self::provider_event::ProviderEvent; +pub use self::provider_event::{ProviderEvent, ProviderEventType}; pub use self::runtime_event::{RuntimeEvent, RuntimeEventKind}; #[cfg(feature = "sgx")] pub use self::sgx_credentials::SgxCredentials; +#[doc(hidden)] pub const ACTIVITY_API_PATH: &str = "/activity-api/v1"; diff --git a/model/src/activity/activity_state.rs b/model/src/activity/activity_state.rs index ed0b1911..17f2d9d2 100644 --- a/model/src/activity/activity_state.rs +++ b/model/src/activity/activity_state.rs @@ -1,17 +1,9 @@ -/* - * Yagna Activity API - * - * It conforms with capability level 1 of the [Activity API specification](https://golem-network.gitbook.io/golem-internal-documentation-test/golem-activity-protocol/golem-activity-api). - * - * The version of the OpenAPI document: v1 - * - * Generated by: https://openapi-generator.tech - */ - use serde::{Deserialize, Serialize}; +/// Reported activity state #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ActivityState { + /// Pair with current state and optional pending state. #[serde(rename = "state")] pub state: StatePair, /// Reason for Activity termination (specified when Activity in Terminated state). @@ -23,6 +15,7 @@ pub struct ActivityState { } impl ActivityState { + /// `true` if activity is terminated or during termination. pub fn alive(&self) -> bool { self.state.alive() } @@ -48,12 +41,14 @@ impl From for ActivityState { } } +/// Pair with current state and optional pending state. #[derive( Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] pub struct StatePair(pub State, pub Option); impl StatePair { + /// `true` if activity is terminated or during termination. pub fn alive(&self) -> bool { !matches!( (&self.0, &self.1), @@ -61,6 +56,7 @@ impl StatePair { ) } + /// Creates transition state from current state to new state. pub fn to_pending(&self, state: State) -> Self { StatePair(self.0, Some(state)) } @@ -72,15 +68,24 @@ impl From for StatePair { } } +/// Represents activity state. #[derive( Clone, Default, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] pub enum State { + /// Activity is new and uninitialized. #[default] New, + /// Activity is configured (agreement data is read). Initialized, + /// Activity is ready to start, deploy command ends successfully. + /// Activity can be started with start command. Deployed, + /// Activity is running. Ready, + /// Activity is terminated. Terminated, + /// Running activity that for some reason + /// is not reporting its state for some time. Unresponsive, } diff --git a/model/src/activity/activity_usage.rs b/model/src/activity/activity_usage.rs index 7b004c86..02edeb9b 100644 --- a/model/src/activity/activity_usage.rs +++ b/model/src/activity/activity_usage.rs @@ -10,11 +10,13 @@ use serde::{Deserialize, Serialize}; +/// Struct for reporting current usage vector. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ActivityUsage { /// Current usage vector #[serde(rename = "currentUsage", skip_serializing_if = "Option::is_none")] pub current_usage: Option>, + /// Report timestamp in number of non-leap seconds since January 1, 1970 0:00:00 UTC (aka “UNIX timestamp”). #[serde(rename = "timestamp")] pub timestamp: i64, } diff --git a/model/src/activity/create_activity.rs b/model/src/activity/create_activity.rs index ff8ad8d9..3691a67f 100644 --- a/model/src/activity/create_activity.rs +++ b/model/src/activity/create_activity.rs @@ -3,15 +3,19 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "sgx")] use crate::activity::sgx_credentials::SgxCredentials; +/// Create new activity request. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CreateActivityRequest { + /// Agreement identifier for which we want to create activity. #[serde(rename = "agreementId")] pub agreement_id: String, + /// For secure computing (sgx) scenario, it is requestor public key needed for e2e encyprtion. #[serde(rename = "requestorPubKey", skip_serializing_if = "Option::is_none")] pub requestor_pub_key: Option, } impl CreateActivityRequest { + /// New request for given agreement id. pub fn new(agreement_id: String) -> CreateActivityRequest { CreateActivityRequest { agreement_id, @@ -20,15 +24,19 @@ impl CreateActivityRequest { } } +/// Create new activity response. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CreateActivityResult { + /// Id of new activity. #[serde(rename = "activityId")] pub activity_id: String, + /// For secure computing (sgx) scenario, it is compute unit credentials. #[serde(rename = "credentials", skip_serializing_if = "Option::is_none")] pub credentials: Option, } impl CreateActivityResult { + /// Creates result for simple scenario pub fn new(activity_id: String) -> CreateActivityResult { CreateActivityResult { activity_id, @@ -37,6 +45,7 @@ impl CreateActivityResult { } } +#[doc(hidden)] #[derive(Clone, Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum Credentials { diff --git a/model/src/activity/exe_script_command.rs b/model/src/activity/exe_script_command.rs index df72e5c2..e51af5c6 100644 --- a/model/src/activity/exe_script_command.rs +++ b/model/src/activity/exe_script_command.rs @@ -1,126 +1,207 @@ -/* - * Yagna Activity API - * - * It conforms with capability level 1 of the [Activity API specification](https://golem-network.gitbook.io/golem-internal-documentation-test/golem-activity-protocol/golem-activity-api). - * - * The version of the OpenAPI document: v1 - * - * - */ - use crate::activity::ExeScriptCommandState; +use crate::NodeId; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr}; +/// Exe-unit control commands. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ExeScriptCommand { + #[doc(hidden)] Sign {}, + /// Configures the container. + /// Downloads the image and verifies the state. Deploy { - #[serde(default)] + /// For exe-units with network abstraction defines network + /// list of network interfaces. + #[serde(default, skip_serializing_if = "Vec::is_empty")] net: Vec, - #[serde(default)] - hosts: HashMap, // hostname -> IP + /// Static hostname mapping. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + hosts: HashMap, // hostname -> IP }, + /// Starts container. Start { + /// Entry point arguments. + /// + /// - `{"start": {"args": [] }}` - runs entry point with empty arguments. + /// - `{"start": {}}` - runs entry point with default arguments. + /// + #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] - args: Vec, + args: Option>, }, + /// Executes process Run { + /// Binary path inside container of executable to run. entry_point: String, + /// Arguments to pass to the executable. #[serde(default)] args: Vec, + /// Specifies whether and how to capture the output of the executed process. #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] capture: Option, }, + /// Transfers files or directories between the host and the container or + /// between containers. + /// + /// ## Example + /// + /// ```json + /// {"transfer": { + /// "from": "http://some-site/data.zip", + /// "to": "container:/app//in/data.zip" + /// } + /// } + /// Transfer { + /// Source path. from: String, + /// Destination path. to: String, + /// Transfer args. #[serde(flatten)] args: TransferArgs, }, + /// Transfers files or directories between the host and the container. Terminate {}, } +/// Reference to container. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum NetworkActivityRef { + /// For single container on single node + Node(NodeId), + /// More specific addressing by pair node id and activity id. + Activity(NodeId, String), +} + +/// Network specification for deploy command. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Network { + /// Network id, created by network api. pub id: String, - pub ip: String, - pub mask: Option, - pub gateway: Option, - pub node_ip: String, + /// Network ip addr for example: 192.168.0.0 + pub ip: Ipv4Addr, + /// Network mask. For example 255.255.0.0 + pub mask: Option, + /// Default gateway. + pub gateway: Option, + /// Ip for given node. For example 192.168.0.1 + pub node_ip: Ipv4Addr, + /// Static routing rules. #[serde(default)] - pub nodes: HashMap, // IP -> NodeId + pub nodes: HashMap, // IP -> NodeId } +/// Represents the capture configuration for stdout and stderr. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct Capture { + /// Specifies the capture mode for stdout. #[serde(skip_serializing_if = "Option::is_none")] pub stdout: Option, + /// Specifies the capture mode for stderr. #[serde(skip_serializing_if = "Option::is_none")] pub stderr: Option, } +/// Represents the capture mode for stdout or stderr. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum CaptureMode { + /// Captures the output at the end of the process. AtEnd { + /// Specifies the capture part (head, tail, or head and tail). #[serde(skip_serializing_if = "Option::is_none")] #[serde(flatten)] part: Option, + /// Specifies if captured output is binary or text. #[serde(skip_serializing_if = "Option::is_none")] format: Option, }, + /// Captures the output as a continuous stream. Stream { + /// If not None it limits capture buffer. overflowed bytes will be discarded. #[serde(skip_serializing_if = "Option::is_none")] limit: Option, + /// Specifies if captured output is binary or text. #[serde(skip_serializing_if = "Option::is_none")] format: Option, }, } +/// Represents the format for captured output. #[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum CaptureFormat { + /// UTF8 string output #[default] #[serde(alias = "string")] Str, + /// Capture as bytes. #[serde(alias = "binary")] Bin, } +/// Represents the capture part for the output. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum CapturePart { + /// Captures the first n bytes of the output. Head(usize), + /// Captures the last n bytes of the output. Tail(usize), + /// Captures both the n/2 first bytes and n/2 last bytes of the output. HeadTail(usize), } +/// Represents the transfer arguments for the file transfer command. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] pub struct TransferArgs { + /// Specifies the format for the file transfer. + /// + /// - `tar` packs fileset into tar format. + /// - `tar.bz2` packs fileset into tar format compressed with bzip2 algorithm. + /// - `tar.gz` packs fileset into tar format compressed with gzip algorithm. + /// - `tar.xz` packs fileset into tar format compressed with xz algorithm. + /// - `zip` packs fleset into zip format compressed with defalate algorithm. + /// - `zip.0` packs fleset into zip format without compression (store only). + /// pub format: Option, + /// Specifies the scanning depth of the file transfer. pub depth: Option, + /// Specifies the file selection rules for the transfer. pub fileset: Option, } +/// Represents a file set for the file transfer command. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum FileSet { + /// Simple includes pattern. Pattern(SetEntry), + /// Complex pattern with includes/excludes Object(SetEntry), } +/// Represents a file set selection rule. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] pub struct SetObject { + /// Provides a description for the file set selection rule. pub desc: Option, + /// Specifies the includes rules. pub includes: Option>, + /// Specifies the excludes rules. pub excludes: Option>, } +#[doc(hidden)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum SetEntry { @@ -144,7 +225,7 @@ impl From for ExeScriptCommandState { ExeScriptCommand::Start { args } => ExeScriptCommandState { command: "Start".to_string(), progress: None, - params: Some(args), + params: args, }, ExeScriptCommand::Run { entry_point, diff --git a/model/src/activity/exe_script_command_result.rs b/model/src/activity/exe_script_command_result.rs index a61fe74c..693759fc 100644 --- a/model/src/activity/exe_script_command_result.rs +++ b/model/src/activity/exe_script_command_result.rs @@ -1,37 +1,41 @@ -/* - * Yagna Activity API - * - * It conforms with capability level 1 of the [Activity API specification](https://golem-network.gitbook.io/golem-internal-documentation-test/golem-activity-protocol/golem-activity-api). - * - * The version of the OpenAPI document: v1 - * - * Generated by: https://openapi-generator.tech - */ - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +/// Represents the result of an single [crate::activity::ExeScriptCommand] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExeScriptCommandResult { + /// The index of the command. pub index: u32, + /// The result of the command. pub result: CommandResult, + /// The stdout output of the command, if any. pub stdout: Option, + /// The stderr output of the command, if any. pub stderr: Option, + /// Error message related to the command result, if any. pub message: Option, + /// Indicates whether the command is part of a finished batch. pub is_batch_finished: bool, + /// The event date of the command execution. pub event_date: DateTime, } +/// Represents the output of a command. #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(untagged)] pub enum CommandOutput { + /// Output represented as a string. Str(String), + /// Output represented as binary data. Bin(Vec), } +/// Represents the result of a command execution. #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub enum CommandResult { + /// The command executed successfully. Ok, + /// An error occurred during command execution. Error, } diff --git a/model/src/activity/exe_script_command_state.rs b/model/src/activity/exe_script_command_state.rs index 206158ce..744c4864 100644 --- a/model/src/activity/exe_script_command_state.rs +++ b/model/src/activity/exe_script_command_state.rs @@ -1,26 +1,23 @@ -/* - * Yagna Activity API - * - * It conforms with capability level 1 of the [Activity API specification](https://golem-network.gitbook.io/golem-internal-documentation-test/golem-activity-protocol/golem-activity-api). - * - * The version of the OpenAPI document: v1 - * - * Generated by: https://openapi-generator.tech - */ - use serde::{Deserialize, Serialize}; +/// Represents the state of an `ExeScriptCommand`. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ExeScriptCommandState { #[serde(rename = "command")] + /// The command name. pub command: String, + #[serde(rename = "progress", skip_serializing_if = "Option::is_none")] + /// The progress of the command execution, if available. pub progress: Option, + #[serde(rename = "params", skip_serializing_if = "Option::is_none")] + /// The parameters of the command. pub params: Option>, } impl ExeScriptCommandState { + /// Creates a new `ExeScriptCommandState` with the specified command name. pub fn new(command: String) -> ExeScriptCommandState { ExeScriptCommandState { command, diff --git a/model/src/activity/exe_script_request.rs b/model/src/activity/exe_script_request.rs index d395cb04..27a73e18 100644 --- a/model/src/activity/exe_script_request.rs +++ b/model/src/activity/exe_script_request.rs @@ -10,13 +10,16 @@ use serde::{Deserialize, Serialize}; +/// Represents a request for executing a script. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ExeScriptRequest { #[serde(rename = "text")] + /// The script text to execute. pub text: String, } impl ExeScriptRequest { + /// Creates a new `ExeScriptRequest` with the specified script text. pub fn new(text: String) -> ExeScriptRequest { ExeScriptRequest { text } } diff --git a/model/src/activity/provider_event.rs b/model/src/activity/provider_event.rs index fa2ad353..4c32bce5 100644 --- a/model/src/activity/provider_event.rs +++ b/model/src/activity/provider_event.rs @@ -11,22 +11,34 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +/// Represents an event related to a provider. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ProviderEvent { + /// The activity ID associated with the event. pub activity_id: String, + + /// The agreement ID associated with the event. pub agreement_id: String, + + /// The type of the provider event. pub event_type: ProviderEventType, + + /// The date and time of the event. pub event_date: DateTime, } +/// Represents the type of a provider event. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ProviderEventType { #[serde(rename = "CreateActivity")] + /// Indicates the creation of an activity. CreateActivity { #[serde(rename = "requestorPubKey", skip_serializing_if = "Option::is_none")] + /// The public key of the requestor associated with the activity creation. requestor_pub_key: Option, }, #[serde(rename = "DestroyActivity")] + /// Indicates the destruction of an activity. DestroyActivity {}, } diff --git a/model/src/activity/runtime_event.rs b/model/src/activity/runtime_event.rs index a1890047..8b756b61 100644 --- a/model/src/activity/runtime_event.rs +++ b/model/src/activity/runtime_event.rs @@ -2,15 +2,24 @@ use crate::activity::{CommandOutput, ExeScriptCommand}; use chrono::{NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; +/// Represents an event related to the runtime. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RuntimeEvent { + /// The batch ID associated with the event. pub batch_id: String, + + /// The index of the event. pub index: usize, + + /// The timestamp of the event. pub timestamp: NaiveDateTime, + + /// The kind of the runtime event. pub kind: RuntimeEventKind, } impl RuntimeEvent { + /// Creates a new `RuntimeEvent` with the specified parameters. pub fn new(batch_id: String, index: usize, kind: RuntimeEventKind) -> Self { RuntimeEvent { batch_id, @@ -20,10 +29,12 @@ impl RuntimeEvent { } } + /// Creates a new `RuntimeEvent` for a started command. pub fn started(batch_id: String, idx: usize, command: ExeScriptCommand) -> Self { Self::new(batch_id, idx, RuntimeEventKind::Started { command }) } + /// Creates a new `RuntimeEvent` for a finished command. pub fn finished( batch_id: String, idx: usize, @@ -40,25 +51,35 @@ impl RuntimeEvent { ) } + /// Creates a new `RuntimeEvent` for stdout output. pub fn stdout(batch_id: String, idx: usize, out: CommandOutput) -> Self { Self::new(batch_id, idx, RuntimeEventKind::StdOut(out)) } + /// Creates a new `RuntimeEvent` for stderr output. pub fn stderr(batch_id: String, idx: usize, out: CommandOutput) -> Self { Self::new(batch_id, idx, RuntimeEventKind::StdErr(out)) } } +/// Represents the kind of a runtime event. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum RuntimeEventKind { + /// Indicates a started command. Started { + /// The executed command. command: ExeScriptCommand, }, + /// Indicates a finished command. Finished { + /// The return code of the command. return_code: i32, + /// Additional message related to the command execution. message: Option, }, + /// Indicates stdout output. StdOut(CommandOutput), + /// Indicates stderr output. StdErr(CommandOutput), } diff --git a/model/src/error_message.rs b/model/src/error_message.rs index 57b9b731..32f0358f 100644 --- a/model/src/error_message.rs +++ b/model/src/error_message.rs @@ -1,22 +1,15 @@ -/* - * Yagna Activity API - * - * It conforms with capability level 1 of the [Activity API specification](https://golem-network.gitbook.io/golem-internal-documentation-test/golem-activity-protocol/golem-activity-api). - * - * The version of the OpenAPI document: v1 - * - * Generated by: https://openapi-generator.tech - */ - use serde::{Deserialize, Serialize}; +/// Generic for api call error. #[derive(thiserror::Error, Clone, Debug, PartialEq, Serialize, Deserialize, Default)] #[error("Yagna API error: {message:?}")] pub struct ErrorMessage { + /// The error message. pub message: Option, } impl ErrorMessage { + /// Creates a new `ErrorMessage` with the specified message. pub fn new(message: impl ToString) -> ErrorMessage { ErrorMessage { message: Some(message.to_string()), diff --git a/model/src/lib.rs b/model/src/lib.rs index 3fa15500..7df8793a 100644 --- a/model/src/lib.rs +++ b/model/src/lib.rs @@ -1,8 +1,15 @@ +#![deny(missing_docs)] +//! +//! Golem REST Api schema types. +//! pub mod activity; -pub mod error_message; +mod error_message; pub mod market; pub mod net; -pub mod node_id; +mod node_id; + +#[doc(hidden)] +pub mod p2p; pub mod payment; pub use error_message::ErrorMessage; diff --git a/model/src/market.rs b/model/src/market.rs index b856305d..46075161 100644 --- a/model/src/market.rs +++ b/model/src/market.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] pub mod agreement; pub mod agreement_event; pub mod agreement_proposal; diff --git a/model/src/net.rs b/model/src/net.rs index 1d3acc3c..c7b864c2 100644 --- a/model/src/net.rs +++ b/model/src/net.rs @@ -1,57 +1,223 @@ +//! API for creating and managing exe-units networking. +//! +//! - VPN - Virtual Private Networks +//! - Internet Gateway +//! use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, Ipv4Addr, SocketAddrV4}; use crate::NodeId; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; -pub const NET_API_PATH: &str = "/net-api/v2"; -pub const NET_API_V1_VPN_PATH: &str = "/net-api/v1"; -pub const NET_API_V2_NET_PATH: &str = "/net-api/v2/net"; -pub const NET_API_V2_VPN_PATH: &str = "/net-api/v2/vpn"; +#[doc(hidden)] +pub const NET_API_PATH: &str = "/net-api/v1"; -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +/// Represents a new network configuration. +#[derive(Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "debug", derive(Debug))] #[serde(rename_all = "camelCase")] -pub struct Status { - pub node_id: NodeId, - pub listen_ip: Option, - pub public_ip: Option, - pub sessions: usize, +pub struct NewNetwork { + /// The IP address for the network. + pub ip: Ipv4Addr, + + /// The subnet mask for the network. + pub mask: Ipv4Addr, + + /// The gateway IP address for the network. Optional. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub gateway: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +/// Represents a network configuration. +#[derive(Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "debug", derive(Debug))] #[serde(rename_all = "camelCase")] pub struct Network { + /// The ID of the network. pub id: String, - pub ip: String, - pub mask: String, - pub gateway: String, -} -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct NewNetwork { - pub ip: String, - pub mask: Option, - pub gateway: Option, + /// The IP address for the network. + pub ip: Ipv4Addr, + + /// The subnet mask for the network. + pub mask: Ipv4Addr, + + /// The gateway IP address for the network. Optional. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub gateway: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +/// Represents single VPN node. +#[derive(Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "debug", derive(Debug))] #[serde(rename_all = "camelCase")] pub struct Node { - pub id: String, - pub ip: String, + /// Provider node id assigned to given ip. + pub id: NodeId, + /// Container IP in given network. + pub ip: IpAddr, + /// Activity id for case when mode than one container runs on given provider. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub activity_id: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +/// Requestor's address in a virtual private network. +#[derive(Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "debug", derive(Debug))] #[serde(rename_all = "camelCase")] pub struct Address { - pub ip: String, + /// The IP address of the requestor in the virtual private network. + pub ip: Ipv4Addr, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +/// Represents a network connection. +#[derive(Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "debug", derive(Debug))] #[serde(rename_all = "camelCase")] pub struct Connection { + /// The protocol of the connection. pub protocol: u16, + + /// The local IP address of the connection. pub local_ip: String, + + /// The local port number of the connection. pub local_port: u16, + + /// The remote IP address of the connection. pub remote_ip: String, + + /// The remote port number of the connection. pub remote_port: u16, } + +/// Represents proxy binding. +#[derive(Clone, Serialize, Deserialize)] +#[cfg_attr( + feature = "json-schema", + derive(JsonSchema), + schemars(example = "example_proxy") +)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[serde(rename_all = "camelCase")] +pub enum Proxy { + /// Forwards TCP or UDP connections to remote address inside vpn. + #[serde(rename_all = "camelCase")] + Forward { + /// Rule type tcp4/udp4 + protocol: Protocol, + /// Address to listen on. It is host address for forward rules or virtual adddress + /// for reverse proxy. + from_local: SocketAddrV4, + /// Ip address inside vpn where to forward connections. + to_remote: SocketAddrV4, + }, + /// Forwards TCP or UDP connections from an address inside VPN to an address on the local host. + #[serde(rename_all = "camelCase")] + Reverse { + /// Rule type tcp4/udp4 + protocol: Protocol, + /// Address to listen on. It is host address for forward rules or virtual adddress + /// for reverse proxy. + from_remote: SocketAddrV4, + /// Ip address in local network to forwards connection. + to_local: SocketAddrV4, + }, +} + +#[cfg(feature = "json-schema")] +fn example_proxy() -> Proxy { + Proxy::Reverse { + protocol: Protocol::Tcp4, + from_remote: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 9000), + to_local: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9000), + } +} + +/// Proxy rule +#[derive(Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "debug", derive(Debug))] +#[serde(rename_all = "camelCase")] +pub enum ProxyRule { + /// Forwards TCP or UDP connections to remote address inside vpn. + #[serde(rename_all = "camelCase")] + Forward { + /// Rule type tcp4/udp4 + protocol: Protocol, + /// Ip address inside vpn where to forward connections. + to_remote: SocketAddrV4, + }, + /// Forwards TCP or UDP connections from an address inside VPN to an address on the local host. + #[serde(rename_all = "camelCase")] + Reverse { + /// Rule type tcp4/udp4 + protocol: Protocol, + + /// Ip address in local network to forwards connection. + to_local: SocketAddrV4, + }, +} + +/// Forwarding protocol +#[derive(Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "debug", derive(Debug))] +#[serde(rename_all = "camelCase")] +pub enum Protocol { + /// Tcp on ipv4 + Tcp4, + /// Udp on ipv4 + Udp4, +} + +#[cfg(test)] +mod test { + use super::*; + use serde_json; + use std::net::Ipv4Addr; + + #[test] + fn test_serialize() { + const MASK: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0); + + let json = serde_json::to_string(&Network { + id: "id".to_string(), + ip: "192.168.0.1".parse().unwrap(), + mask: MASK, + gateway: None, + }) + .unwrap(); + + eprintln!("json={}", json); + } + + #[test] + fn test_serialize_proxy() { + let _json = serde_json::to_string(&Proxy::Reverse { + protocol: Protocol::Tcp4, + from_remote: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 9000), + to_local: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9000), + }) + .unwrap(); + + let _: Proxy = serde_json::from_str( + r#"{ + "reverse": { + "protocol": "tcp4", + "fromRemote": "0.0.0.0:9000", + "toLocal": "127.0.0.1:9000" + } + }"#, + ) + .unwrap(); + } +} diff --git a/model/src/node_id.rs b/model/src/node_id.rs index ef5c3d6c..7a234206 100644 --- a/model/src/node_id.rs +++ b/model/src/node_id.rs @@ -6,10 +6,15 @@ use std::{fmt, str}; const NODE_ID_LENGTH: usize = 20; +const NODE_ID_STR_LENGTH: usize = NODE_ID_LENGTH * 2 + 2; + +/// Represents an error that occurs during parsing of a NodeId. #[derive(Clone, Debug, thiserror::Error, PartialEq, Serialize, Deserialize)] #[error("NodeId `{original_str}` parsing error: {msg}")] pub struct ParseError { + /// The original string that failed to parse as a NodeId. original_str: String, + /// The error message describing the parsing failure. msg: String, } @@ -34,6 +39,39 @@ pub struct NodeId { inner: [u8; NODE_ID_LENGTH], } +#[cfg(feature = "json-schema")] +use schemars::{ + gen::SchemaGenerator, + schema::{InstanceType, Schema, SchemaObject, StringValidation}, + JsonSchema, +}; + +#[cfg(feature = "json-schema")] +impl JsonSchema for NodeId { + fn is_referenceable() -> bool { + false + } + + fn schema_name() -> String { + "String".to_owned() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + SchemaObject { + instance_type: Some(InstanceType::String.into()), + string: Some( + StringValidation { + pattern: Some("0x[0-9a-fA-F]{40}".into()), + ..Default::default() + } + .into(), + ), + ..Default::default() + } + .into() + } +} + impl NodeId { #[inline(always)] fn with_hex(&self, f: F) -> R @@ -59,6 +97,7 @@ impl NodeId { f(hex_str) } + /// Converts node id into 20 bytes. #[inline] pub fn into_array(self) -> [u8; NODE_ID_LENGTH] { self.inner @@ -123,44 +162,52 @@ impl<'a> From> for NodeId { } #[inline] -fn hex_to_dec(hex: u8, s: &str) -> Result { +fn hex_to_dec(hex: u8, s: &[u8]) -> Result { match hex { b'A'..=b'F' => Ok(hex - b'A' + 10), b'a'..=b'f' => Ok(hex - b'a' + 10), b'0'..=b'9' => Ok(hex - b'0'), _ => Err(ParseError::new( - s, + String::from_utf8_lossy(s).into_owned(), format!("expected hex chars, but got: `{}`", char::from(hex)), )), } } -impl str::FromStr for NodeId { - type Err = ParseError; +fn from_str_bytes(bytes: &[u8]) -> Result { + // String representation is 2x the byte length + 2 extra for prefix + if bytes.len() != NODE_ID_STR_LENGTH { + return Err(ParseError::new( + String::from_utf8_lossy(bytes), + "expected length is 42 chars", + )); + } - fn from_str(s: &str) -> Result { - let bytes = s.as_bytes(); + if bytes[0] != b'0' || bytes[1] != b'x' { + return Err(ParseError::new( + String::from_utf8_lossy(bytes), + "expected 0x prefix", + )); + } - // String representation is 2x the byte length + 2 extra for prefix - if bytes.len() != 2 + NODE_ID_LENGTH * 2 { - return Err(ParseError::new(s, "expected length is 42 chars")); - } + let mut inner = [0u8; NODE_ID_LENGTH]; + let mut p = 0; - if bytes[0] != b'0' || bytes[1] != b'x' { - return Err(ParseError::new(s, "expected 0x prefix")); - } + for b in bytes[2..].chunks(2) { + let (hi, lo) = (hex_to_dec(b[0], bytes)?, hex_to_dec(b[1], bytes)?); + inner[p] = (hi << 4) | lo; + p += 1; + } + assert_eq!(p, NODE_ID_LENGTH); - let mut inner = [0u8; NODE_ID_LENGTH]; - let mut p = 0; + Ok(NodeId { inner }) +} - for b in bytes[2..].chunks(2) { - let (hi, lo) = (hex_to_dec(b[0], s)?, hex_to_dec(b[1], s)?); - inner[p] = (hi << 4) | lo; - p += 1; - } - assert_eq!(p, NODE_ID_LENGTH); +impl FromStr for NodeId { + type Err = ParseError; - Ok(NodeId { inner }) + fn from_str(s: &str) -> Result { + from_str_bytes(s.as_bytes()) } } @@ -174,7 +221,11 @@ impl Serialize for NodeId { where S: Serializer, { - self.with_hex(|hex_str| serializer.serialize_str(hex_str)) + if serializer.is_human_readable() || cfg!(feature = "node-id-compat") { + self.with_hex(|hex_str| serializer.serialize_str(hex_str)) + } else { + serializer.serialize_bytes(&self.inner) + } } } @@ -213,7 +264,9 @@ impl<'de> de::Visitor<'de> for NodeIdVisit { where E: de::Error, { - if v.len() == NODE_ID_LENGTH { + if v.len() == NODE_ID_LENGTH * 2 + 2 { + Ok(from_str_bytes(v).map_err(|_| de::Error::custom("invalid format"))?) + } else if v.len() == NODE_ID_LENGTH { let mut inner = [0u8; NODE_ID_LENGTH]; inner.copy_from_slice(v); Ok(NodeId { inner }) @@ -228,7 +281,7 @@ impl<'de> Deserialize<'de> for NodeId { where D: Deserializer<'de>, { - deserializer.deserialize_str(NodeIdVisit) + deserializer.deserialize_bytes(NodeIdVisit) } } @@ -369,4 +422,16 @@ mod tests { ) ); } + + #[test] + fn test_serialize() { + let n1 = NodeId::from_str("0xbabe000000000000000000000000000000000707").unwrap(); + let v = flexbuffers::to_vec(&n1).unwrap(); + let vj = serde_json::to_string(&n1).unwrap(); + eprintln!("size={}/{}", v.len(), vj.len()); + eprintln!("v={:?}", v); + eprintln!("vj={}", vj); + assert_eq!(flexbuffers::from_slice::(&v).unwrap(), n1); + assert_eq!(serde_json::from_str::(&vj).unwrap(), n1); + } } diff --git a/model/src/p2p.rs b/model/src/p2p.rs new file mode 100644 index 00000000..351ce8da --- /dev/null +++ b/model/src/p2p.rs @@ -0,0 +1,16 @@ +use crate::NodeId; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +pub const NET_API_PATH: &str = "/net-api/v2"; +pub const NET_API_V2_NET_PATH: &str = "/net-api/v2/net"; +pub const NET_API_V2_VPN_PATH: &str = "/net-api/v2/vpn"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Status { + pub node_id: NodeId, + pub listen_ip: Option, + pub public_ip: Option, + pub sessions: usize, +} diff --git a/model/src/payment.rs b/model/src/payment.rs index dc273483..f861f875 100644 --- a/model/src/payment.rs +++ b/model/src/payment.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] pub mod acceptance; pub mod account; pub mod activity_payment; diff --git a/specs/net-api.yaml b/specs/net-api.yaml index 6814845c..7b8cbadd 100644 --- a/specs/net-api.yaml +++ b/specs/net-api.yaml @@ -1,6 +1,6 @@ openapi: 3.0.2 info: - version: 0.1.0 + version: 0.1.2 title: Yagna Net API description: 'Yagna Net API' @@ -8,7 +8,7 @@ servers: - url: /net-api/v1 security: - - app_key: [] + - app_key: [ ] tags: - name: requestor @@ -24,8 +24,6 @@ paths: responses: 200: $ref: '#/components/responses/NetworkList' - 400: - $ref: 'common.yaml#/responses/BadRequest' 401: $ref: 'common.yaml#/responses/Unauthorized' default: @@ -217,10 +215,31 @@ paths: description: > WebSocket endpoint for establishing a TCP connection to `{ip}:{port}`. get: - operationId: connect_tcp + operationId: connectTcpTo + security: + - app_key: [ ] + - app_key_param: [ ] + responses: + 101: + description: 'Switches to WebSocket on request with protocol upgrade header.' + 401: + $ref: 'common.yaml#/responses/Unauthorized' + 404: + $ref: 'common.yaml#/responses/NotFound' + + /net/{networkId}/listen-tcp/{port}: + parameters: + - $ref: '#/components/parameters/networkId' + - $ref: '#/components/parameters/port' + description: > + WebSocket endpoint for listening on `{port}`. + get: + tags: + - requestor + operationId: listenTcpOn security: - - app_key: [] - - app_key_param: [] + - app_key: [ ] + - app_key_param: [ ] responses: 101: description: 'Switches to WebSocket on request with protocol upgrade header.' @@ -229,6 +248,31 @@ paths: 404: $ref: 'common.yaml#/responses/NotFound' + + /net/{networkId}/proxy: + parameters: + - $ref: '#/components/parameters/networkId' + get: + tags: + - requestor + operationId: listNetworkProxy + responses: + '200': + $ref: '#/components/responses/ProxyList' + 401: + $ref: 'common.yaml#/responses/Unauthorized' + 404: + $ref: 'common.yaml#/responses/NotFound' + post: + tags: + - requestor + operationId: createNetworkProxy + requestBody: + $ref: '#/components/requestBodies/NewProxy' + responses: + 201: + description: 'Proxy Created' + components: securitySchemes: @@ -292,6 +336,13 @@ components: schema: $ref: '#/components/schemas/Node' + NewProxy: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Proxy' + responses: AddressList: @@ -337,6 +388,15 @@ components: items: $ref: '#/components/schemas/Connection' + ProxyList: + description: List of Proxys created by the Requestor. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Proxy' + schemas: Address: @@ -358,24 +418,32 @@ components: properties: protocol: type: integer + minimum: 0 + format: uint16 readOnly: true localIp: type: string readOnly: true localPort: type: integer + minimum: 0 + format: uint16 readOnly: true remoteIp: type: string readOnly: true remotePort: type: integer + minimum: 0 + format: uint16 readOnly: true Network: type: object required: + - id - ip + - mask properties: id: type: string @@ -393,6 +461,77 @@ components: - ip properties: id: + description: Provider's node identifier type: string ip: + description: ipv4 adddress + type: string + format: ipv4 + activityId: + description: > + for the scenario when more than one container operates on + one provider within the VPN, the container ID is needed to + unambiguously direct traffic. type: string + + Protocol: + type: string + enum: + - tcp4 + - udp4 + + Proxy: + example: + reverse: + protocol: tcp4 + fromRemote: 0.0.0.0:9000 + toLocal: 127.0.0.1:9000 + oneOf: + - description: Forwards TCP or UDP connections to remote address inside vpn. + type: object + required: + - forward + properties: + forward: + type: object + required: + - fromLocal + - protocol + - toRemote + properties: + fromLocal: + description: Address to listen on. It is host address for forward rules or virtual adddress for reverse proxy. + type: string + protocol: + description: Rule type tcp4/udp4 + allOf: + - $ref: '#/components/schemas/Protocol' + toRemote: + description: Ip address inside vpn where to forward connections. + type: string + additionalProperties: false + - description: Forwards TCP or UDP connections from an address inside VPN to an address on the local host. + type: object + required: + - reverse + properties: + reverse: + type: object + required: + - fromRemote + - protocol + - toLocal + properties: + fromRemote: + description: Address to listen on. It is host address for forward rules or virtual adddress for reverse proxy. + type: string + protocol: + description: Rule type tcp4/udp4 + allOf: + - $ref: '#/components/schemas/Protocol' + toLocal: + description: Ip address in local network to forwards connection. + type: string + additionalProperties: false + + diff --git a/src/activity.rs b/src/activity.rs index 65fd09a3..7dff78c2 100644 --- a/src/activity.rs +++ b/src/activity.rs @@ -1,13 +1,16 @@ //! Activity part of the Yagna API -mod provider; -mod requestor; +#[cfg(feature = "provider")] +mod provider; +#[cfg(feature = "provider")] pub use provider::ActivityProviderApi; -pub use requestor::control::ActivityRequestorControlApi; -pub use requestor::state::ActivityRequestorStateApi; -pub use requestor::ActivityRequestorApi; -#[cfg(feature = "sgx")] +#[cfg(feature = "requestor")] +mod requestor; +#[cfg(feature = "requestor")] +pub use requestor::{ActivityRequestorApi, ActivityRequestorControlApi, ActivityRequestorStateApi}; + +#[cfg(all(feature = "sgx", feature = "requestor"))] pub use requestor::control::sgx::SecureActivityRequestorApi; pub(crate) const ACTIVITY_URL_ENV_VAR: &str = "YAGNA_ACTIVITY_URL"; diff --git a/src/activity/requestor.rs b/src/activity/requestor.rs index 18e91cbb..9d96d8eb 100644 --- a/src/activity/requestor.rs +++ b/src/activity/requestor.rs @@ -41,3 +41,6 @@ impl ActivityRequestorApi { self.client.get(&uri).send().json().await } } + +pub use control::ActivityRequestorControlApi; +pub use state::ActivityRequestorStateApi; diff --git a/src/cli.rs b/src/cli.rs index 88908420..070639c0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use structopt::StructOpt; use url::Url; -use crate::{activity, market, net, payment, web::WebClient, web::WebInterface}; +use crate::{web::WebClient, web::WebInterface}; use std::convert::TryFrom; use crate::activity::ACTIVITY_URL_ENV_VAR; @@ -12,11 +12,25 @@ use crate::web::{DEFAULT_YAGNA_API_URL, YAGNA_API_URL_ENV_VAR}; const YAGNA_APPKEY_ENV_VAR: &str = "YAGNA_APPKEY"; +#[cfg(feature = "provider")] +mod provider; +#[cfg(feature = "provider")] +pub use provider::*; + +#[cfg(feature = "requestor")] +mod requestor; +#[cfg(feature = "requestor")] +pub use requestor::*; + +pub trait WebInterfaceFactory { + fn try_from_client(client: WebClient) -> anyhow::Result; +} + pub trait ApiClient: Clone { - type Market: WebInterface; - type Activity: WebInterface; - type Payment: WebInterface; - type Net: WebInterface; + type Market: WebInterfaceFactory; + type Activity: WebInterfaceFactory; + type Payment: WebInterfaceFactory; + type Net: WebInterfaceFactory; } #[derive(Clone)] @@ -27,28 +41,6 @@ pub struct Api { pub net: T::Net, } -pub type RequestorApi = Api; -pub type ProviderApi = Api; - -#[derive(Clone)] -pub struct Requestor; -#[derive(Clone)] -pub struct Provider; - -impl ApiClient for Requestor { - type Market = market::MarketRequestorApi; - type Activity = activity::ActivityRequestorApi; - type Payment = payment::PaymentApi; - type Net = net::NetApi; -} - -impl ApiClient for Provider { - type Market = market::MarketProviderApi; - type Activity = activity::ActivityProviderApi; - type Payment = payment::PaymentApi; - type Net = net::NetApi; -} - #[derive(StructOpt, Clone)] #[structopt(rename_all = "kebab-case")] pub struct ApiOpts { diff --git a/src/cli/provider.rs b/src/cli/provider.rs new file mode 100644 index 00000000..b7956537 --- /dev/null +++ b/src/cli/provider.rs @@ -0,0 +1,14 @@ +use super::{Api, ApiClient}; +use crate::{activity, market, p2p, payment}; + +#[derive(Clone)] +pub struct Provider; + +pub type ProviderApi = Api; + +impl ApiClient for Provider { + type Market = market::MarketProviderApi; + type Activity = activity::ActivityProviderApi; + type Payment = payment::PaymentApi; + type Net = p2p::NetApi; +} diff --git a/src/cli/requestor.rs b/src/cli/requestor.rs new file mode 100644 index 00000000..8dc84774 --- /dev/null +++ b/src/cli/requestor.rs @@ -0,0 +1,13 @@ +use super::{Api, ApiClient}; +use crate::{activity, market, net, payment}; +pub type RequestorApi = Api; + +#[derive(Clone)] +pub struct Requestor; + +impl ApiClient for Requestor { + type Market = market::MarketRequestorApi; + type Activity = activity::ActivityRequestorApi; + type Payment = payment::PaymentApi; + type Net = net::NetVpnApi; +} diff --git a/src/error.rs b/src/error.rs index d3fecc1d..206118a5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -55,6 +55,8 @@ pub enum Error { EventStreamError(String), } +pub type Result = std::result::Result; + impl From for Error { fn from(e: PayloadError) -> Self { Error::PayloadError(e) diff --git a/src/lib.rs b/src/lib.rs index 49e5bc97..8de309ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,12 @@ #[macro_use] pub mod web; +#[cfg(any(feature = "requestor", feature = "provider", feature = "cli"))] pub mod activity; pub mod market; +#[cfg(feature = "requestor")] pub mod net; +pub mod p2p; pub mod payment; pub mod error; diff --git a/src/net.rs b/src/net.rs index b75a7583..d61d9833 100644 --- a/src/net.rs +++ b/src/net.rs @@ -11,28 +11,6 @@ pub const NET_URL_ENV_VAR: &str = "YAGNA_NET_URL"; pub type Result = std::result::Result; -/// Bindings for Requestor part of the Net API. -#[derive(Clone)] -pub struct NetApi { - client: WebClient, -} - -impl WebInterface for NetApi { - const API_URL_ENV_VAR: &'static str = "YAGNA_NET_URL"; - const API_SUFFIX: &'static str = ya_client_model::net::NET_API_V2_NET_PATH; - - fn from_client(client: WebClient) -> Self { - NetApi { client } - } -} - -impl NetApi { - /// Retrieves connection status. - pub async fn get_status(&self) -> Result { - self.client.get("status").send().json().await - } -} - /// Bindings for Requestor part of the Net VPN API. #[deprecated(since = "0.7.0", note = "Please use `NetVpnApi` instead")] pub type NetRequestorApi = NetVpnApi; @@ -45,7 +23,7 @@ pub struct NetVpnApi { impl WebInterface for NetVpnApi { const API_URL_ENV_VAR: &'static str = "YAGNA_NET_URL"; - const API_SUFFIX: &'static str = ya_client_model::net::NET_API_V2_VPN_PATH; + const API_SUFFIX: &'static str = ya_client_model::net::NET_API_PATH; fn from_client(client: WebClient) -> Self { NetVpnApi { client } diff --git a/src/p2p.rs b/src/p2p.rs new file mode 100644 index 00000000..6daf0c29 --- /dev/null +++ b/src/p2p.rs @@ -0,0 +1,25 @@ +use crate::error::Result; +use crate::web::{WebClient, WebInterface}; +use ya_client_model::p2p::Status; + +/// Bindings for Requestor part of the Net API. +#[derive(Clone)] +pub struct NetApi { + client: WebClient, +} + +impl WebInterface for NetApi { + const API_URL_ENV_VAR: &'static str = "YAGNA_NET_URL"; + const API_SUFFIX: &'static str = ya_client_model::p2p::NET_API_V2_NET_PATH; + + fn from_client(client: WebClient) -> Self { + NetApi { client } + } +} + +impl NetApi { + /// Retrieves connection status. + pub async fn get_status(&self) -> Result { + self.client.get("status").send().json().await + } +}