diff --git a/Cargo.lock b/Cargo.lock index 31601a41..7dcd1a99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3093,6 +3093,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "url", ] [[package]] @@ -3743,6 +3744,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] diff --git a/crates/synd_term/gql/schema.json b/crates/synd_term/gql/schema.json index 1cd032e9..f398fdfc 100644 --- a/crates/synd_term/gql/schema.json +++ b/crates/synd_term/gql/schema.json @@ -345,7 +345,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "FeedUrl", "ofType": null } } @@ -751,7 +751,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "FeedUrl", "ofType": null } } @@ -771,7 +771,7 @@ { "args": [], "deprecationReason": null, - "description": null, + "description": "Category of the feed", "isDeprecated": false, "name": "category", "type": { @@ -828,6 +828,16 @@ "name": "FeedType", "possibleTypes": null }, + { + "description": null, + "enumValues": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "SCALAR", + "name": "FeedUrl", + "possibleTypes": null + }, { "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).", "enumValues": null, @@ -1504,7 +1514,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "FeedUrl", "ofType": null } } @@ -1741,7 +1751,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "FeedUrl", "ofType": null } } diff --git a/crates/synd_term/src/application/input_parser.rs b/crates/synd_term/src/application/input_parser.rs index 0adc4025..f33ffcc0 100644 --- a/crates/synd_term/src/application/input_parser.rs +++ b/crates/synd_term/src/application/input_parser.rs @@ -76,7 +76,8 @@ mod feed { sequence::{delimited, Tuple}, Finish, IResult, Parser, }; - use synd_feed::types::Category; + use synd_feed::types::{Category, FeedUrl}; + use url::Url; use super::NomError; use crate::{ @@ -129,11 +130,22 @@ mod feed { )) } - fn url(s: &str) -> IResult<&str, String> { - map(take_while(|c: char| !c.is_whitespace()), |s: &str| { + fn url(s: &str) -> IResult<&str, FeedUrl> { + let (remain, url) = map(take_while(|c: char| !c.is_whitespace()), |s: &str| { s.to_owned() }) - .parse(s) + .parse(s)?; + match Url::parse(&url) { + Ok(url) => Ok((remain, FeedUrl::from(url))), + Err(err) => { + // TODO: represents parse error as type + tracing::warn!("Invalid url: {err}"); + Err(nom::Err::Failure(nom::error::Error::new( + remain, + nom::error::ErrorKind::TakeWhile1, + ))) + } + } } #[cfg(test)] @@ -166,7 +178,7 @@ mod feed { Ok(( "", SubscribeFeedInput { - url: "https://example.ymgyt.io/atom.xml".into(), + url: "https://example.ymgyt.io/atom.xml".try_into().unwrap(), requirement: Some(Requirement::MUST), category: Some(Category::new("rust").unwrap()) } diff --git a/crates/synd_term/src/application/mod.rs b/crates/synd_term/src/application/mod.rs index 1621465d..24c27796 100644 --- a/crates/synd_term/src/application/mod.rs +++ b/crates/synd_term/src/application/mod.rs @@ -6,6 +6,7 @@ use ratatui::{style::palette::tailwind, widgets::Widget}; use synd_auth::device_flow::{ self, DeviceAccessTokenResponse, DeviceAuthorizationResponse, DeviceFlow, }; +use synd_feed::types::FeedUrl; use tokio::time::{Instant, Sleep}; use crate::{ @@ -630,7 +631,7 @@ impl Application { self.jobs.futures.push(fut); } - fn unsubscribe_feed(&mut self, url: String) { + fn unsubscribe_feed(&mut self, url: FeedUrl) { let client = self.client.clone(); let request_seq = self.in_flight.add(RequestId::UnsubscribeFeed); let fut = async move { diff --git a/crates/synd_term/src/client/mod.rs b/crates/synd_term/src/client/mod.rs index 1706db9f..1eb87a5b 100644 --- a/crates/synd_term/src/client/mod.rs +++ b/crates/synd_term/src/client/mod.rs @@ -27,7 +27,7 @@ pub mod query; #[derive(Error, Debug)] pub enum SubscribeFeedError { #[error("invalid feed url: `{feed_url}` ({message})`")] - InvalidFeedUrl { feed_url: String, message: String }, + InvalidFeedUrl { feed_url: FeedUrl, message: String }, #[error("internal error: {0}")] Internal(anyhow::Error), } @@ -118,7 +118,7 @@ impl Client { } #[tracing::instrument(skip(self))] - pub async fn unsubscribe_feed(&self, url: String) -> anyhow::Result<()> { + pub async fn unsubscribe_feed(&self, url: FeedUrl) -> anyhow::Result<()> { let var = mutation::unsubscribe_feed::Variables { unsubscribe_input: mutation::unsubscribe_feed::UnsubscribeFeedInput { url }, }; diff --git a/crates/synd_term/src/client/mutation.rs b/crates/synd_term/src/client/mutation.rs index 50f0a37f..40653775 100644 --- a/crates/synd_term/src/client/mutation.rs +++ b/crates/synd_term/src/client/mutation.rs @@ -16,6 +16,7 @@ pub mod subscribe_feed { #[allow(dead_code)] type ID = String; type Category = crate::client::scalar::Category; + type FeedUrl = crate::client::scalar::FeedUrl; type Rfc3339Time = crate::client::scalar::Rfc3339Time; #[derive(Clone, Debug, Eq, PartialEq)] pub enum FeedType { @@ -112,7 +113,7 @@ pub mod subscribe_feed { } #[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct SubscribeFeedInput { - pub url: String, + pub url: FeedUrl, pub requirement: Option, pub category: Option, } @@ -128,7 +129,7 @@ pub mod subscribe_feed { #[serde(rename = "type")] pub type_: FeedType, pub title: Option, - pub url: String, + pub url: FeedUrl, pub updated: Option, #[serde(rename = "websiteUrl")] pub website_url: Option, @@ -227,6 +228,7 @@ pub mod unsubscribe_feed { type Int = i64; #[allow(dead_code)] type ID = String; + type FeedUrl = crate::client::scalar::FeedUrl; #[derive(Clone, Debug, Eq, PartialEq)] pub enum ResponseCode { OK, @@ -260,7 +262,7 @@ pub mod unsubscribe_feed { } #[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct UnsubscribeFeedInput { - pub url: String, + pub url: FeedUrl, } #[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct Variables { diff --git a/crates/synd_term/src/client/query.rs b/crates/synd_term/src/client/query.rs index b9ccda0e..37fb87ac 100644 --- a/crates/synd_term/src/client/query.rs +++ b/crates/synd_term/src/client/query.rs @@ -16,6 +16,7 @@ pub mod subscription { #[allow(dead_code)] type ID = String; type Category = crate::client::scalar::Category; + type FeedUrl = crate::client::scalar::FeedUrl; type Rfc3339Time = crate::client::scalar::Rfc3339Time; #[derive(Clone, Debug, Eq, PartialEq)] pub enum FeedType { @@ -91,7 +92,7 @@ pub mod subscription { #[serde(rename = "type")] pub type_: FeedType, pub title: Option, - pub url: String, + pub url: FeedUrl, pub updated: Option, #[serde(rename = "websiteUrl")] pub website_url: Option, @@ -184,6 +185,7 @@ pub mod entries { #[allow(dead_code)] type ID = String; type Category = crate::client::scalar::Category; + type FeedUrl = crate::client::scalar::FeedUrl; type Rfc3339Time = crate::client::scalar::Rfc3339Time; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Requirement { @@ -233,7 +235,7 @@ pub mod entries { #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] pub struct FeedMeta { pub title: Option, - pub url: String, + pub url: FeedUrl, pub requirement: Option, pub category: Option, } @@ -288,6 +290,7 @@ pub mod export_subscription { type Int = i64; #[allow(dead_code)] type ID = String; + type FeedUrl = crate::client::scalar::FeedUrl; #[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct Variables { pub after: Option, @@ -318,7 +321,7 @@ pub mod export_subscription { #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] pub struct ExportSubscriptionOutputFeedsNodes { pub title: Option, - pub url: String, + pub url: FeedUrl, } } impl graphql_client::GraphQLQuery for ExportSubscription { diff --git a/crates/synd_term/src/client/scalar.rs b/crates/synd_term/src/client/scalar.rs index 5a3dce58..6ceb42be 100644 --- a/crates/synd_term/src/client/scalar.rs +++ b/crates/synd_term/src/client/scalar.rs @@ -1,2 +1,3 @@ pub type Category = synd_feed::types::Category<'static>; +pub type FeedUrl = synd_feed::types::FeedUrl; pub type Rfc3339Time = String; diff --git a/crates/synd_term/src/command.rs b/crates/synd_term/src/command.rs index 202166df..f909976b 100644 --- a/crates/synd_term/src/command.rs +++ b/crates/synd_term/src/command.rs @@ -1,6 +1,6 @@ use std::fmt::Display; use synd_auth::device_flow::{DeviceAccessTokenResponse, DeviceAuthorizationResponse}; -use synd_feed::types::Category; +use synd_feed::types::{Category, FeedUrl}; use crate::{ application::{Direction, ListAction, RequestSequence}, @@ -48,14 +48,14 @@ pub enum Command { input: SubscribeFeedInput, }, UnsubscribeFeed { - url: String, + url: FeedUrl, }, CompleteSubscribeFeed { feed: Feed, request_seq: RequestSequence, }, CompleteUnsubscribeFeed { - url: String, + url: FeedUrl, request_seq: RequestSequence, }, FetchSubscription { diff --git a/crates/synd_term/src/types/mod.rs b/crates/synd_term/src/types/mod.rs index cf3dcae0..573bb467 100644 --- a/crates/synd_term/src/types/mod.rs +++ b/crates/synd_term/src/types/mod.rs @@ -1,7 +1,7 @@ use chrono::DateTime; use schemars::JsonSchema; use serde::Serialize; -use synd_feed::types::{Category, FeedType, Requirement}; +use synd_feed::types::{Category, FeedType, FeedUrl, Requirement}; use crate::{ client::{ @@ -92,7 +92,7 @@ impl EntryMeta { pub struct Feed { pub r#type: Option, pub title: Option, - pub url: String, + pub url: FeedUrl, pub updated: Option