Skip to content

Commit

Permalink
Fix / implement setup page
Browse files Browse the repository at this point in the history
  • Loading branch information
RReverser committed Sep 29, 2024
1 parent 9dcde08 commit 783cf00
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 82 deletions.
42 changes: 5 additions & 37 deletions src/api/autogen/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import openapi from '@readme/openapi-parser';
import { chmod, open, unlink, writeFile } from 'fs/promises';
import { unlink, writeFile } from 'fs/promises';
import { spawnSync } from 'child_process';
import {
toSnakeCase,
toPascalCase as toTypeName,
toPascalCase
} from 'js-convert-case';
import { OpenAPIV3_1, OpenAPIV3 } from 'openapi-types';
import { toSnakeCase, toPascalCase } from 'js-convert-case';
import { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
import * as assert from 'assert/strict';
import { CanonicalDevice, canonicalDevices } from './xml-names.js';
import { rustKeywords } from './rust-keywords.js';
Expand Down Expand Up @@ -72,7 +68,7 @@ function nameAndTarget<T>(ref: T) {
let { $ref } = ref as ReferenceObject;
return {
target: ($ref ? _refs.get($ref) : ref) as Exclude<T, ReferenceObject>,
name: $ref && toTypeName($ref.match(/([^/]+)$/)![1])
name: $ref && toPascalCase($ref.match(/([^/]+)$/)![1])
};
}

Expand Down Expand Up @@ -359,7 +355,7 @@ class DeviceMethod {
${stringifyDoc(this.doc)}
#[http("${this.path}", method = ${
this.method
}${this.returnType.maybeVia()})]
}${this.returnType.maybeVia()})]
async fn ${this.name}(
&self,
${this.resolvedArgs.toString(
Expand Down Expand Up @@ -401,34 +397,6 @@ class Device {
? 'std::fmt::Debug + Send + Sync'
: 'Device + Send + Sync'
} {
${
this.isBaseDevice
? `
const EXTRA_METHODS: () = {
/// Static device name for the configured list.
fn static_name(&self) -> &str {
&self.name
}
/// Unique ID of this device.
fn unique_id(&self) -> &str {
&self.unique_id
}
};
/// Web page user interface that enables device specific configuration to be set for each available device.
///
/// The server should implement this to return HTML string. You can use [\`Self::action\`] to store the configuration.
///
/// Note: on the client side you almost never want to just retrieve HTML to show it in the browser, as that breaks relative URLs.
/// Use the \`/{device_type}/{device_number}/setup\` URL instead.
#[http("setup", method = Get)]
async fn setup(&self) -> ASCOMResult<String> {
Ok(include_str!("../server/device_setup_template.html").to_owned())
}
`
: ''
}
${this.methods}
}
`;
Expand Down
23 changes: 0 additions & 23 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,29 +434,6 @@ pub enum TelescopeAxis {
/// ASCOM Methods Common To All Devices.
#[apply(rpc_trait)]
pub trait Device: std::fmt::Debug + Send + Sync {
const EXTRA_METHODS: () = {
/// Static device name for the configured list.
fn static_name(&self) -> &str {
&self.name
}

/// Unique ID of this device.
fn unique_id(&self) -> &str {
&self.unique_id
}
};

/// Web page user interface that enables device specific configuration to be set for each available device.
///
/// The server should implement this to return HTML string. You can use [`Self::action`] to store the configuration.
///
/// Note: on the client side you almost never want to just retrieve HTML to show it in the browser, as that breaks relative URLs.
/// Use the `/{device_type}/{device_number}/setup` URL instead.
#[http("setup", method = Get)]
async fn setup(&self) -> ASCOMResult<String> {
Ok(include_str!("../server/device_setup_template.html").to_owned())
}

/// Actions and SupportedActions are a standardised means for drivers to extend functionality beyond the built-in capabilities of the ASCOM device interfaces.
///
/// The key advantage of using Actions is that drivers can expose any device specific functionality required. The downside is that, in order to use these unique features, every application author would need to create bespoke code to present or exploit them.
Expand Down
2 changes: 1 addition & 1 deletion src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl RawDeviceClient {
}
}

static REQWEST: Lazy<reqwest::Client> = Lazy::new(|| {
pub(crate) static REQWEST: Lazy<reqwest::Client> = Lazy::new(|| {
reqwest::Client::builder()
.user_agent("ascom-alpaca-rs")
.build()
Expand Down
98 changes: 77 additions & 21 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,74 @@ pub(crate) use auto_increment;
allow(unused_macro_rules)
)]
macro_rules! rpc_trait {
(@extra_trait_methods Device) => {
/// Static device name for the configured list.
fn static_name(&self) -> &str;

/// Unique ID of this device.
fn unique_id(&self) -> &str;

/// Web page user interface that enables device specific configuration to be set for each available device.
///
/// The server should implement this to return HTML string. You can use [`Self::action`] to store the configuration.
///
/// Note: on the client side you almost never want to just retrieve HTML to show it in the browser, as that breaks relative URLs.
/// Use the `/{device_type}/{device_number}/setup` URL instead.
///
/// Definition before the `#[async_trait]` expansion:
/// ```ignore
/// async fn setup(&self) -> eyre::Result<String>
/// # { unimplemented!() }
/// ```
// Note: this method is not part of the ASCOM API, which is why we need to inject it manually.
// Because we inject it manually, we also need to manually expand the async_trait macro.
fn setup<'this, 'async_trait>(
&'this self,
) -> futures::future::BoxFuture<'async_trait, eyre::Result<String>>
where
'this: 'async_trait,
Self: 'async_trait
{
Box::pin(async {
Ok(include_str!("../server/device_setup_template.html").to_owned())
})
}
};
(@extra_trait_methods $trait_name:ident) => {};

(@extra_trait_client_impls Device) => {
fn static_name(&self) -> &str {
&self.name
}

fn unique_id(&self) -> &str {
&self.unique_id
}

fn setup<'this, 'async_trait>(
&'this self,
) -> futures::future::BoxFuture<'async_trait, eyre::Result<String>>
where
'this: 'async_trait,
Self: 'async_trait
{
Box::pin(async move {
Ok(
$crate::client::REQWEST
.get(self.inner.base_url.join("setup")?)
.send()
.await?
.text()
.await?
)
})
}
};
(@extra_trait_client_impls $trait_name:ident) => {};

(
$(# $attr:tt)*
$pub:vis trait $trait_name:ident: $($first_parent:ident)::+ $(+ $($other_parents:ident)::+)* {
$(
const EXTRA_METHODS: () = {
$(
$(#[doc = $extra_method_doc:literal])*
$($extra_method_name:ident)+ ($($extra_method_params:tt)*) $(-> $extra_method_return:ty)? $extra_method_client_impl:block
)*
};
)?

$(
$(#[doc = $doc:literal])*
#[http($method_path:literal, method = $http_method:ident $(, via = $via:path)?)]
Expand Down Expand Up @@ -96,12 +152,7 @@ macro_rules! rpc_trait {
#[async_trait::async_trait]
#[allow(unused_variables)]
$pub trait $trait_name: $($first_parent)::+ $(+ $($other_parents)::+)* {
$(
$(
$(#[doc = $extra_method_doc])*
$($extra_method_name)+ ($($extra_method_params)*) $(-> $extra_method_return)?;
)*
)?
rpc_trait!(@extra_trait_methods $trait_name);

$(
$(#[doc = $doc])*
Expand Down Expand Up @@ -150,11 +201,7 @@ macro_rules! rpc_trait {
#[cfg(feature = "client")]
#[async_trait::async_trait]
impl $trait_name for $crate::client::RawDeviceClient {
$(
$(
$($extra_method_name)+ ($($extra_method_params)*) $(-> $extra_method_return)? $extra_method_client_impl
)*
)?
rpc_trait!(@extra_trait_client_impls $trait_name);

$(
#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -409,8 +456,8 @@ macro_rules! rpc_mod {
};
)*

#[cfg(feature = "server")]
impl Devices {
#[cfg(feature = "server")]
pub(crate) async fn handle_action<'this>(&'this self, device_type: DeviceType, device_number: usize, action: &'this str, params: $crate::server::ActionParams) -> $crate::server::Result<TypedResponse> {
let action = TypedDeviceAction::from_parts(device_type, action, params)?;

Expand All @@ -430,6 +477,15 @@ macro_rules! rpc_mod {
}?)
})
}

pub(crate) async fn get_setup_html(&self, device_type: DeviceType, device_number: usize) -> eyre::Result<String> {
match device_type {
$(
#[cfg(feature = $path)]
DeviceType::$trait_name => self.get_for_server::<dyn $trait_name>(device_number)?.setup().await,
)*
}
}
}

#[cfg(test)]
Expand Down
13 changes: 13 additions & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,19 @@ impl Server {
}
}

// Setup endpoint is not an ASCOM method, so doesn't need the transaction and ASCOMResult wrapping.
if action == "setup" {
let result = devices.get_setup_html(device_type, device_number).await;
let result = match result {
Ok(html) => Ok(axum::response::Html(html)),
Err(err) => Err((
http::StatusCode::INTERNAL_SERVER_ERROR,
format!("{err:#}"),
)),
};
return result.into_response();
}

server_handler
.exec(|params| {
devices.handle_action(device_type, device_number, &action, params)
Expand Down

0 comments on commit 783cf00

Please sign in to comment.