twuebi commented on issue #599:
URL: https://github.com/apache/iceberg-rust/issues/599#issuecomment-2333642793

   Hi @liurenjie1024, 
   
   
https://github.com/hansetag/iceberg-catalog/blob/213560059d69248f6f67a979ce2cc3c1e808602f/crates/iceberg-catalog/src/service/storage/s3.rs#L317
 and 
https://github.com/hansetag/iceberg-catalog/blob/213560059d69248f6f67a979ce2cc3c1e808602f/crates/iceberg-catalog/src/service/storage/az.rs#L174
 would be two locations showcasing writing of properties.
   
   The first location uses well-known keys, so we can make use of our type-safe 
properties. The second example has dynamic keys for azdls sas tokens, hence we 
use the `CustomConfig` type here which has fewer guarantees. 
   
   
https://github.com/hansetag/iceberg-catalog/blob/213560059d69248f6f67a979ce2cc3c1e808602f/crates/iceberg-catalog/src/catalog/tables.rs#L1039-L1043
 constructs the config struct using the unchecked path and then fetches the 
location from it.
   
   The creation API of TableProperties can be seen here: 
https://github.com/hansetag/iceberg-catalog/blob/213560059d69248f6f67a979ce2cc3c1e808602f/crates/iceberg-ext/src/configs/table.rs.
   
   Declaration looks like this:
   
   ```rust
   // creates Properties struct & marker trait
   impl_properties!(TableProperties, TableProperty);
   
   pub mod s3 {
       use super::super::ConfigProperty;
       use super::{ConfigParseError, NotCustomProp, ParseFromStr, 
TableProperties, TableProperty};
       use crate::configs::impl_config_values;
       use url::Url;
   
       impl_config_values!(
           Table, // prefix used for the property newtypes
           {
               // Suffix of the newtype, type of the newtype, key of the 
newtype, name of the accessor function
               Region, String, "s3.region", "s3_region";
               Endpoint, Url, "s3.endpoint", "s3_endpoint";
               PathStyleAccess, bool, "s3.path-style-access", 
"s3_path_style_access";
               AccessKeyId, String, "s3.access-key-id", "s3_access_key_id";
               SecretAccessKey, String, "s3.secret-access-key", 
"s3_secret_access_key";
               SessionToken, String, "s3.session-token", "s3_session_token";
               RemoteSigningEnabled, bool, "s3.remote-signing-enabled", 
"s3_remote_signing_enabled";
               Signer, String, "s3.signer", "s3_signer";
               SignerUri, String, "s3.signer.uri", "s3_signer_uri";
            }
       );
   }
   ```
   
   Expanding both the  `impl_properties!` and `impl_config_values!` macros then 
yields this:
   
   ## impl_properties!
   
   ```rust
   #[derive(Debug, PartialEq, Default)]
   pub struct TableProperties {
       props: std::collections::HashMap<String, String>,
   }
   pub trait TableProperty: ConfigProperty {}
   impl TableProperties {
       ///   Inserts a property into the configuration. 
       ///
       ///   If the property already exists, the previous value is returned. 
       pub fn insert<S: TableProperty>(&mut self, pair: &S) -> Option<S::Type> {
           let prev = self
               .props
               .insert(pair.key().to_string(), pair.value_to_string());
           prev.and_then(|v| S::parse_value(v.as_str()).ok())
       }
   
       ///   Removes a property from the configuration. 
       ///
       ///   If the property exists, the value is returned. 
       ///   If the property exists but cannot be parsed to the expected type,  
`None`  is returned. 
       pub fn remove<S: TableProperty + NotCustomProp>(&mut self) -> 
Option<S::Type> {
           self.props
               .remove(S::KEY)
               .and_then(|v| S::parse_value(v.as_str()).ok())
       }
   
       ///   Removes a property from the configuration. 
       ///
       ///   If the property exists, the value is returned. 
       pub fn remove_untyped(&mut self, key: &str) -> Option<String> {
           self.props.remove(key)
       }
   
       #[must_use]
       ///   Get a property from the configuration. 
       ///
       ///   If the property exists but cannot be parsed to the expected type,  
`None`  is returned. 
       ///   If you want to fail on unparsable values, use  `get_prop_fallible` 
. 
       pub fn get_prop_opt<C: TableProperty + NotCustomProp>(&self) -> 
Option<C::Type> {
           self.props
               .get(C::KEY)
               .and_then(|v| ParseFromStr::parse_value(v.as_str()).ok())
       }
   
       #[must_use]
       ///   Get a property from the configuration. 
       ///
       ///   If the property does not exist  `None`  is returend 
       ///
       ///   # Errors 
       ///
       ///   If the property exists but cannot be parsed to the expected type, 
a  `ConfigParseError`  is returned. 
       pub fn get_prop_fallible<C: TableProperty + NotCustomProp>(
           &self,
       ) -> Option<Result<C::Type, ConfigParseError>> {
           self.props
               .get(C::KEY)
               .map(|v| ParseFromStr::parse_value(v.as_str()))
               .map(|r| r.map_err(|e| e.for_key(C::KEY)))
       }
   
       #[must_use]
       ///   Get a custom property from the configuration. 
       ///
       ///   A custom property is a property that is either dynamic in its key 
or a property that 
       ///   is not part of the standard interface. As such it is not bound to 
a specific type. 
       ///   Both key and value are strings. 
       pub fn get_custom_prop(&self, key: &str) -> Option<String> {
           self.props.get(key).cloned()
       }
   
       ///   Convenience constructor to create a new configuration from an 
optional list of 
       ///   properties. 
       ///
       ///   # Errors 
       ///
       ///   Returns an error if a known key has an incompatible value. 
       pub fn try_from_maybe_props(
           props: Option<impl IntoIterator<Item=(String, String)>>,
       ) -> Result<Self, ConfigParseError> {
           match props {
               Some(props) => Self::try_from_props(props),
               None => Ok(Self::default()),
           }
       }
   
       #[must_use]
       ///   Non validating constructor to create a new configuration from a 
list of properties. 
       ///
       ///   Useful when going from wire format ( `HashMap<String, String>` ) 
to this type when 
       ///   failing on a single invalid property is not desired.  
`get_prop_fallible`  still 
       ///   offers the possibility to have runtime errors above unexpected 
failures elsewhere. 
       pub fn from_props_unchecked(props: impl IntoIterator<Item=(String, 
String)>) -> Self {
           let mut config = Self::default();
           for (key, value) in props {
               config.props.insert(key, value);
           }
           config
       }
   }
   ```
   
   ## impl_config_values
   
   ```rust
   use crate::configs::impl_config_value;
   #[derive(Debug, PartialEq, Clone)]
   pub struct Endpoint(pub Url);
   impl ConfigProperty for Endpoint {
       const KEY: &'static str = "s3.endpoint";
       type Type = Url;
   
       fn value(&self) -> &Self::Type {
           &self.0
       }
   
       fn into_value(self) -> Self::Type {
           self.0
       }
   
       fn parse_value(value: &str) -> Result<Self::Type, ConfigParseError>
       where
           Self::Type: ParseFromStr,
       {
           Self::Type::parse_value(value).map_err(|e| e.for_key(Self::KEY))
       }
   }
   impl NotCustomProp for Endpoint {}
   impl TableProperty for Endpoint {}
   impl TableProperties {
       #[must_use]
       pub fn s3_endpoint(&self) -> Option<Url> {
           self.get_prop_opt::<Endpoint>()
       }
   
       pub fn insert_s3_endpoint(&mut self, value: Url) -> Option<Url> {
           self.insert(&Endpoint(value))
       }
   }
   ...
   
   #[derive(Debug, PartialEq, Clone)]
   pub struct SignerUri(pub String);
   impl ConfigProperty for SignerUri {
       const KEY: &'static str = "s3.signer.uri";
       type Type = String;
   
       fn value(&self) -> &Self::Type {
           &self.0
       }
   
       fn into_value(self) -> Self::Type {
           self.0
       }
   
       fn parse_value(value: &str) -> Result<Self::Type, ConfigParseError>
       where
           Self::Type: ParseFromStr,
       {
           Self::Type::parse_value(value).map_err(|e| e.for_key(Self::KEY))
       }
   }
   impl NotCustomProp for SignerUri {}
   impl TableProperty for SignerUri {}
   impl TableProperties {
       #[must_use]
       pub fn s3_signer_uri(&self) -> Option<String> {
           self.get_prop_opt::<SignerUri>()
       }
   
       pub fn insert_s3_signer_uri(&mut self, value: String) -> Option<String> {
           self.insert(&SignerUri(value))
       }
   }
   pub(crate) fn validate(
       key: &str,
       value: &str,
   ) -> Result<(), ConfigParseError> {
       Ok(match key {
           Region::KEY => {
               _ = Region::parse_value(value)?;
           }
           Endpoint::KEY => {
               _ = Endpoint::parse_value(value)?;
           }
           PathStyleAccess::KEY => {
               _ = PathStyleAccess::parse_value(value)?;
           }
           AccessKeyId::KEY => {
               _ = AccessKeyId::parse_value(value)?;
           }
           SecretAccessKey::KEY => {
               _ = SecretAccessKey::parse_value(value)?;
           }
           SessionToken::KEY => {
               _ = SessionToken::parse_value(value)?;
           }
           RemoteSigningEnabled::KEY => {
               _ = RemoteSigningEnabled::parse_value(value)?;
           }
           Signer::KEY => {
               _ = Signer::parse_value(value)?;
           }
           SignerUri::KEY => {
               _ = SignerUri::parse_value(value)?;
           }
           _ => {}
       })
   }
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org
For additional commands, e-mail: issues-h...@iceberg.apache.org

Reply via email to