diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 72f1f1e42..4da5fbd5b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -248,7 +248,7 @@ jobs: - name: Build all run: cargo build --all-targets --all-features - name: Test all - run: cargo test --all-targets --all-features + run: cargo test --all-targets --features=reflection,fetch,dcf_info ############ Figma resources figma-resources: runs-on: ubuntu-latest diff --git a/.idea/runConfigurations/Fetch_HelloWorld.xml b/.idea/runConfigurations/Fetch_HelloWorld.xml new file mode 100644 index 000000000..5fcb9578a --- /dev/null +++ b/.idea/runConfigurations/Fetch_HelloWorld.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Figma_Fetch_Tests.xml b/.idea/runConfigurations/Figma_Fetch_Tests.xml new file mode 100644 index 000000000..d192fb2ec --- /dev/null +++ b/.idea/runConfigurations/Figma_Fetch_Tests.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1be7622f1..dcb6d8cc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,7 +414,6 @@ dependencies = [ "layout", "lazy_static", "log", - "phf 0.11.2", "prost", "serde", "serde-generate", @@ -761,38 +760,18 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", + "phf_macros", + "phf_shared", "proc-macro-hack", ] -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros 0.11.2", - "phf_shared 0.11.2", -] - [[package]] name = "phf_generator" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ - "phf_shared 0.10.0", - "rand", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared 0.11.2", + "phf_shared", "rand", ] @@ -802,27 +781,14 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator", + "phf_shared", "proc-macro-hack", "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.72", -] - [[package]] name = "phf_shared" version = "0.10.0" @@ -832,15 +798,6 @@ dependencies = [ "siphasher 0.3.11", ] -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher 0.3.11", -] - [[package]] name = "png" version = "0.17.13" @@ -1137,7 +1094,7 @@ checksum = "f8c9331265d81c61212dc75df7b0836544ed8e32dba77a522f113805ff9a948e" dependencies = [ "heck 0.3.3", "include_dir", - "phf 0.10.1", + "phf", "serde", "serde-reflection", "textwrap", diff --git a/crates/dc_jni/src/jni.rs b/crates/dc_jni/src/jni.rs index 555129251..269b5305f 100644 --- a/crates/dc_jni/src/jni.rs +++ b/crates/dc_jni/src/jni.rs @@ -28,7 +28,7 @@ use jni::sys::{jint, JNI_VERSION_1_6}; use jni::{JNIEnv, JavaVM}; use lazy_static::lazy_static; -use log::{error, LevelFilter}; +use log::{error, info, LevelFilter}; lazy_static! { static ref JAVA_VM: Mutex>> = Mutex::new(None); @@ -126,9 +126,19 @@ fn jni_fetch_doc_impl( let request: ConvertRequest = serde_json::from_str(&request_json)?; let convert_result: figma_import::ConvertResponse = - match fetch_doc(&doc_id, &version_id, request, proxy_config).map_err(Error::from) { + match fetch_doc(&doc_id, &version_id, &request, proxy_config).map_err(Error::from) { Ok(it) => it, Err(err) => { + let queries_string = request + .queries + .iter() + .map(|q| format!("--nodes=\"{}\" ", q)) + .collect::>() + .join(" "); + + info!("Failed to fetch {}, Try fetching locally", doc_id); + info!("fetch --doc-id={} --version-id={} {} ", doc_id, version_id, queries_string); + map_err_to_exception(env, &err, doc_id).expect("Failed to throw exception"); return Err(err); } diff --git a/crates/figma_import/Cargo.toml b/crates/figma_import/Cargo.toml index 9852200cf..881d7825d 100644 --- a/crates/figma_import/Cargo.toml +++ b/crates/figma_import/Cargo.toml @@ -10,10 +10,10 @@ rust-version.workspace = true [features] default = [] reflection = ["serde-reflection", "serde-generate", "clap"] -http_mock = ["phf"] fetch = ["clap"] dcf_info = ["clap"] fetch_layout = ["clap"] +test_fetches = ["fetch"] [dependencies] @@ -31,7 +31,6 @@ svgtypes.workspace = true unicode-segmentation.workspace = true image.workspace = true euclid.workspace = true -phf = { workspace = true, optional = true } # layout dependencies taffy.workspace = true diff --git a/crates/figma_import/src/document.rs b/crates/figma_import/src/document.rs index acb8dfca9..5711fc7b7 100644 --- a/crates/figma_import/src/document.rs +++ b/crates/figma_import/src/document.rs @@ -16,7 +16,6 @@ use dc_bundle::definition::element::{ variable_map::NameIdMap, Collection, Mode, Variable, VariableMap, }; use dc_bundle::legacy_definition::element::node::NodeQuery; -#[cfg(not(feature = "http_mock"))] use std::time::Duration; use std::{ collections::{HashMap, HashSet}, @@ -40,13 +39,11 @@ use dc_bundle::legacy_definition::EncodedImageMap; use dc_bundle::legacy_figma_live_update::FigmaDocInfo; use log::error; -#[cfg(not(feature = "http_mock"))] const FIGMA_TOKEN_HEADER: &str = "X-Figma-Token"; const BASE_FILE_URL: &str = "https://api.figma.com/v1/files/"; const BASE_COMPONENT_URL: &str = "https://api.figma.com/v1/components/"; const BASE_PROJECT_URL: &str = "https://api.figma.com/v1/projects/"; -#[cfg(not(feature = "http_mock"))] fn http_fetch(api_key: &str, url: String, proxy_config: &ProxyConfig) -> Result { let mut agent_builder = ureq::AgentBuilder::new(); let mut buffer = Vec::new(); @@ -69,9 +66,6 @@ fn http_fetch(api_key: &str, url: String, proxy_config: &ProxyConfig) -> Result< Ok(body) } -#[cfg(feature = "http_mock")] -use crate::figma_v1_document_mocks::http_fetch; - /// Document update requests return this value to indicate if an update was /// made or not. #[derive(Copy, Clone, PartialEq, Eq, Debug)] diff --git a/crates/figma_import/src/fetch.rs b/crates/figma_import/src/fetch.rs index ff74a751e..50406b2ab 100644 --- a/crates/figma_import/src/fetch.rs +++ b/crates/figma_import/src/fetch.rs @@ -45,7 +45,7 @@ pub enum ProxyConfig { pub struct ConvertRequest<'r> { figma_api_key: &'r str, // Node names - queries: Vec<&'r str>, + pub queries: Vec<&'r str>, // Ignored images ignored_images: Vec>, @@ -65,6 +65,19 @@ pub struct ConvertRequest<'r> { image_session: Option, } +impl<'r> ConvertRequest<'r> { + pub fn new(figma_api_key: &'r str, queries: Vec<&'r str>, version: Option) -> Self { + Self { + figma_api_key, + queries, + ignored_images: Vec::new(), + last_modified: None, + version, + image_session: None, + } + } +} + #[derive(Serialize, Deserialize)] pub enum ConvertResponse { Document(Vec), @@ -74,7 +87,7 @@ pub enum ConvertResponse { pub fn fetch_doc( id: &str, requested_version_id: &str, - rq: ConvertRequest, + rq: &ConvertRequest, proxy_config: &ProxyConfig, ) -> Result { if let Some(mut doc) = Document::new_if_changed( @@ -82,8 +95,8 @@ pub fn fetch_doc( id.into(), requested_version_id.into(), proxy_config, - rq.last_modified.unwrap_or(String::new()), - rq.version.unwrap_or(String::new()), + rq.last_modified.clone().unwrap_or(String::new()), + rq.version.clone().unwrap_or(String::new()), rq.image_session.clone(), )? { // The document has changed since the version the client has, so we should fetch diff --git a/crates/figma_import/src/figma_schema.rs b/crates/figma_import/src/figma_schema.rs index 7064315e3..bd8d18cdd 100644 --- a/crates/figma_import/src/figma_schema.rs +++ b/crates/figma_import/src/figma_schema.rs @@ -1230,6 +1230,7 @@ pub struct VariableAlias { pub enum VariableAliasOrList { Alias(VariableAlias), List(Vec), + Map(HashMap), } impl VariableAliasOrList { fn get_name(&self) -> Option { @@ -1243,6 +1244,9 @@ impl VariableAliasOrList { return Some(alias.id.clone()); } } + VariableAliasOrList::Map(_) => { + panic!("We don't support Maps of Variables!"); + } } None } diff --git a/crates/figma_import/src/lib.rs b/crates/figma_import/src/lib.rs index 955565ec5..84da945a2 100644 --- a/crates/figma_import/src/lib.rs +++ b/crates/figma_import/src/lib.rs @@ -49,8 +49,6 @@ pub use dc_bundle::legacy_definition::element::node::NodeQuery; pub use dc_bundle::legacy_definition::view::view::View; pub use dc_bundle::legacy_definition::view::view::ViewData; -#[cfg(feature = "http_mock")] -mod figma_v1_document_mocks; /// Functionality related to reflection for deserializing our bincode archives in other /// languages #[cfg(feature = "reflection")] diff --git a/crates/figma_import/src/tools/fetch.rs b/crates/figma_import/src/tools/fetch.rs index c44ed524c..9b2e18996 100644 --- a/crates/figma_import/src/tools/fetch.rs +++ b/crates/figma_import/src/tools/fetch.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::io::Write; use crate::{Document, ProxyConfig}; @@ -19,6 +20,7 @@ use crate::{Document, ProxyConfig}; use clap::Parser; use dc_bundle::legacy_definition::element::node::NodeQuery; use dc_bundle::legacy_definition::{DesignComposeDefinition, DesignComposeDefinitionHeader}; +use log::error; #[derive(Debug)] #[allow(dead_code)] @@ -69,47 +71,43 @@ pub struct Args { pub output: std::path::PathBuf, } -pub fn fetch(args: Args) -> Result<(), ConvertError> { - let proxy_config: ProxyConfig = match args.http_proxy { - Some(x) => ProxyConfig::HttpProxyConfig(x), - None => ProxyConfig::None, - }; - - // If the API Key wasn't provided on the path or via env var, load it from ~/.config/figma_access_token - let api_key = args.api_key.unwrap_or_else(|| { - let config_path = std::path::Path::new(&std::env::var("HOME").unwrap()) +pub fn load_figma_token() -> Option { + let env_var = env::var("FIGMA_ACCESS_TOKEN"); + if let Ok(token) = env_var { + Some(token) + } else { + let config_path = std::path::Path::new(&env::var("HOME").unwrap()) .join(".config") .join("figma_access_token"); - std::fs::read_to_string(config_path) - .expect("Could not read API key from ~/.config/figma_access_token") - .trim() - .parse() - .unwrap() - }); + match std::fs::read_to_string(config_path) { + Ok(token) => Some(token.trim().parse().unwrap()), + Err(_) => { + error!("Could not read Figma token from ~/.config/figma_access_token"); + None + } + } + } +} - let mut doc: Document = Document::new( - api_key.as_str(), - args.doc_id, - args.version_id.unwrap_or(String::new()), - &proxy_config, - None, - )?; +pub fn build_definition( + doc: &mut Document, + nodes: &Vec, +) -> Result { let mut error_list = Vec::new(); // Convert the requested nodes from the Figma doc. let views = doc.nodes( - &args.nodes.iter().map(|name| NodeQuery::name(name)).collect(), + &nodes.iter().map(|name| NodeQuery::name(name)).collect(), &Vec::new(), &mut error_list, )?; for error in error_list { eprintln!("Warning: {error}"); } - let variable_map = doc.build_variable_map(); // Build the serializable doc structure - let serializable_doc = DesignComposeDefinition { + Ok(DesignComposeDefinition { views, component_sets: doc.component_sets().clone(), images: doc.encoded_image_map(), @@ -117,8 +115,29 @@ pub fn fetch(args: Args) -> Result<(), ConvertError> { name: doc.get_name(), version: doc.get_version(), id: doc.get_document_id(), - variable_map: variable_map, + variable_map, + }) +} + +pub fn fetch(args: Args) -> Result<(), ConvertError> { + let proxy_config: ProxyConfig = match args.http_proxy { + Some(x) => ProxyConfig::HttpProxyConfig(x), + None => ProxyConfig::None, }; + + // If the API Key wasn't provided on the path or via env var, load it from env or ~/.config/figma_access_token + let api_key = args.api_key.unwrap_or_else(|| load_figma_token().unwrap()); + + let mut doc: Document = Document::new( + api_key.as_str(), + args.doc_id, + args.version_id.unwrap_or(String::new()), + &proxy_config, + None, + )?; + + let dc_definition = build_definition(&mut doc, &args.nodes)?; + println!("Fetched document"); println!(" DC Version: {}", DesignComposeDefinitionHeader::current().version); println!(" Doc ID: {}", doc.get_document_id()); @@ -128,7 +147,7 @@ pub fn fetch(args: Args) -> Result<(), ConvertError> { // We don't bother with serialization of image sessions with this tool. let mut output = std::fs::File::create(args.output)?; let header = bincode::serialize(&DesignComposeDefinitionHeader::current())?; - let doc = bincode::serialize(&serializable_doc)?; + let doc = bincode::serialize(&dc_definition)?; output.write_all(header.as_slice())?; output.write_all(doc.as_slice())?; Ok(()) diff --git a/crates/figma_import/tests/test_fetches.rs b/crates/figma_import/tests/test_fetches.rs new file mode 100644 index 000000000..90f7bcc62 --- /dev/null +++ b/crates/figma_import/tests/test_fetches.rs @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#![cfg(feature = "fetch")] + +use figma_import::tools::fetch::{build_definition, load_figma_token}; +use figma_import::{Document, ProxyConfig}; + +// Simply fetches and serializes a doc +#[test] +#[cfg_attr(not(feature = "test_fetches"), ignore)] +fn fetch_variable_modes() { + const DOC_ID: &str = "HhGxvL4aHhP8ALsLNz56TP"; + const QUERIES: &[&str] = &["#stage", "#Box"]; + let queries: Vec = QUERIES.iter().map(|s| s.to_string()).collect(); + + let figma_token = load_figma_token().unwrap(); + + let mut doc: Document = Document::new( + figma_token.as_str(), + DOC_ID.to_string(), + String::new(), + &ProxyConfig::None, + None, + ) + .unwrap(); + + let dc_definition = build_definition(&mut doc, &queries).unwrap(); + + bincode::serialize(&dc_definition).unwrap(); +}