- 
                Notifications
    
You must be signed in to change notification settings  - Fork 15
 
[FFL-1284] Create datadog-ffe-ffi crate #1282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ffe-pyo3-methods
Are you sure you want to change the base?
Changes from all commits
a0e4bff
              96c3e15
              37764b0
              c927269
              dfc60e3
              5dfbd46
              911cf1f
              47e0a5c
              168718a
              43ec366
              a3c6612
              10298cb
              c453cb9
              1e0586a
              2fcf32b
              7b7af45
              2d11e90
              7a7d500
              006b709
              2f1c2ff
              2d4e32d
              b2481f0
              f49b07f
              3ee6f4b
              61582eb
              8314d12
              b3321aa
              95a04fe
              c0fae17
              b360449
              7afa96b
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||
| # Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||||||||
| # SPDX-License-Identifier: Apache-2.0 | ||||||||
| 
     | 
||||||||
| [package] | ||||||||
| name = "datadog-ffe-ffi" | ||||||||
| edition.workspace = true | ||||||||
| version = "0.1.0" | ||||||||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To match with libdatadog/datadog-ffe/Cargo.toml Line 3 in 7e0063f 
 to indicate that datadog-ffe-ffi is not production ready
    There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we can pin this, so that  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a temporary issue. When FFI is ready, we can update  From Line 68 in a0e4bff 
 Also the version in the dependency should cause a build failure, which is another way of ensuring that they match libdatadog/datadog-ffe-ffi/Cargo.toml Line 24 in c453cb9 
 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we will not use the workspace version. 
  | 
||||||||
| rust-version.workspace = true | ||||||||
| license.workspace = true | ||||||||
| 
     | 
||||||||
| [lib] | ||||||||
| crate-type = ["lib", "staticlib", "cdylib"] | ||||||||
                
      
                  sameerank marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||||||||
| bench = false | ||||||||
| 
     | 
||||||||
| [features] | ||||||||
| default = ["cbindgen"] | ||||||||
| cbindgen = ["build_common/cbindgen", "ddcommon-ffi/cbindgen"] | ||||||||
| 
     | 
||||||||
| [build-dependencies] | ||||||||
| build_common = { path = "../build-common" } | ||||||||
| 
     | 
||||||||
| [dependencies] | ||||||||
| anyhow = "1.0.93" | ||||||||
| datadog-ffe = { path = "../datadog-ffe", version = "=0.1.0" } | ||||||||
| ddcommon-ffi = { path = "../ddcommon-ffi", default-features = false } | ||||||||
| function_name = "0.3.0" | ||||||||
| 
     | 
||||||||
| [dev-dependencies] | ||||||||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| extern crate build_common; | ||
| 
     | 
||
| use build_common::generate_and_configure_header; | ||
| 
     | 
||
| fn main() { | ||
| println!("cargo:rerun-if-changed=src/*"); | ||
| let header_name = "datadog_ffe.h"; | ||
| generate_and_configure_header(header_name); | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| 
     | 
||
| language = "C" | ||
| cpp_compat = true | ||
| tab_width = 2 | ||
| header = """// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| """ | ||
| include_guard = "DDOG_FFE_H" | ||
| style = "tag" | ||
| usize_is_size_t = true | ||
| pragma_once = true | ||
| 
     | 
||
| no_includes = true | ||
| sys_includes = ["stdbool.h", "stddef.h", "stdint.h"] | ||
| includes = ["common.h"] | ||
| 
     | 
||
| [export] | ||
| include = ["datadog-ffe-ffi"] | ||
| prefix = "ddog_ffe_" | ||
| renaming_overrides_prefixing = true | ||
| 
     | 
||
| [export.rename] | ||
| "VoidResult" = "ddog_VoidResult" | ||
| "Error" = "ddog_Error" | ||
| "Vec_u8" = "ddog_Vec_U8" | ||
| 
     | 
||
| [export.mangle] | ||
| rename_types = "PascalCase" | ||
| 
     | 
||
| [enum] | ||
| prefix_with_name = true | ||
| rename_variants = "ScreamingSnakeCase" | ||
| 
     | 
||
| [fn] | ||
| must_use = "DDOG_CHECK_RETURN" | ||
| 
     | 
||
| [parse] | ||
| parse_deps = true | ||
| include = ["ddcommon-ffi", "datadog-ffe"] | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| 
     | 
||
| use std::ffi::{c_char, c_uchar, CStr}; | ||
| 
     | 
||
| use datadog_ffe::rules_based::{ | ||
| now, Assignment, AssignmentValue, Configuration, EvaluationContext, EvaluationError, Str, | ||
| VariationType, | ||
| }; | ||
| 
     | 
||
| use crate::Handle; | ||
| 
     | 
||
| /// Opaque type representing a result of evaluation. | ||
| #[allow(unused)] | ||
| pub struct ResolutionDetails(Result<Assignment, EvaluationError>); | ||
| 
     | 
||
| #[repr(C)] | ||
| pub enum FlagType { | ||
| Unknown, | ||
| String, | ||
| Integer, | ||
| Float, | ||
| Boolean, | ||
| Object, | ||
| } | ||
| 
     | 
||
| impl From<VariationType> for FlagType { | ||
| fn from(value: VariationType) -> Self { | ||
| match value { | ||
| VariationType::String => FlagType::String, | ||
| VariationType::Integer => FlagType::Integer, | ||
| VariationType::Numeric => FlagType::Float, | ||
| VariationType::Boolean => FlagType::Boolean, | ||
| VariationType::Json => FlagType::Object, | ||
| } | ||
| } | ||
| } | ||
| 
     | 
||
| #[repr(C)] | ||
| pub enum ErrorCode { | ||
| Ok, | ||
| TypeMismatch, | ||
| ParseError, | ||
| FlagNotFound, | ||
| TargetingKeyMissing, | ||
| InvalidContext, | ||
| ProviderNotReady, | ||
| General, | ||
| } | ||
| 
     | 
||
| #[repr(C)] | ||
| pub enum Reason { | ||
| Static, | ||
| Default, | ||
| TargetingMatch, | ||
| Split, | ||
| Disabled, | ||
| Error, | ||
| } | ||
| 
     | 
||
| /// Evaluates a feature flag. | ||
| /// | ||
| /// # Ownership | ||
| /// | ||
| /// The caller must call `ddog_ffe_assignment_drop` on the returned value to free resources. | ||
| /// | ||
| /// # Safety | ||
| /// - `config` must be a valid `Configuration` handle | ||
| /// - `flag_key` must be a valid C string | ||
| /// - `context` must be a valid `EvaluationContext` handle | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn ddog_ffe_get_assignment( | ||
| config: Handle<Configuration>, | ||
| flag_key: *const c_char, | ||
| _expected_type: FlagType, | ||
| context: Handle<EvaluationContext>, | ||
| ) -> Handle<ResolutionDetails> { | ||
| if flag_key.is_null() { | ||
| return Handle::from(ResolutionDetails(Err(EvaluationError::Internal( | ||
| Str::from_static_str("ddog_ffe_get_assignment: flag_key must not be NULL"), | ||
| )))); | ||
| } | ||
| 
     | 
||
| let config = unsafe { config.as_ref() }; | ||
| let context = unsafe { context.as_ref() }; | ||
| 
     | 
||
| let Ok(flag_key) = unsafe { | ||
| // SAFETY: we checked that flag_key is not NULL | ||
| CStr::from_ptr(flag_key) | ||
| } | ||
| .to_str() else { | ||
| return Handle::from(ResolutionDetails(Err(EvaluationError::Internal( | ||
| Str::from_static_str("ddog_ffe_get_assignment: flag_key is not a valid UTF-8 string"), | ||
| )))); | ||
| }; | ||
| 
     | 
||
| let assignment_result = config.eval_flag(flag_key, context, None, now()); | ||
| 
     | 
||
| Handle::from(ResolutionDetails(assignment_result)) | ||
| } | ||
| 
     | 
||
| #[repr(C)] | ||
| pub enum VariantValue { | ||
| /// Evaluation did not produce any value. | ||
| None, | ||
| String(*const c_uchar), | ||
| Integer(i64), | ||
| Float(f64), | ||
| Boolean(bool), | ||
| Object(*const c_char), | ||
| } | ||
| 
     | 
||
| /// Get value produced by evaluation. | ||
| /// | ||
| /// # Ownership | ||
| /// | ||
| /// The returned `VariantValue` borrows from `assignment`. It must not be used after `assignment` is | ||
| /// freed. | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn ddog_ffe_assignment_get_value( | ||
| assignment: Handle<ResolutionDetails>, | ||
| ) -> VariantValue { | ||
| match unsafe { assignment.as_ref() } { | ||
| ResolutionDetails(Ok(assignment)) => match &assignment.value { | ||
| AssignmentValue::String(s) => VariantValue::String(s.as_ptr()), | ||
| AssignmentValue::Integer(v) => VariantValue::Integer(*v), | ||
| AssignmentValue::Float(v) => VariantValue::Float(*v), | ||
| AssignmentValue::Boolean(v) => VariantValue::Boolean(*v), | ||
| AssignmentValue::Json(_value) => todo!("make AssignmentValue hold onto raw json value"), | ||
| }, | ||
| _ => VariantValue::None, | ||
| } | ||
| } | ||
| 
     | 
||
| /// Get variant key produced by evaluation. Returns `NULL` if evaluation did not produce any value. | ||
| /// | ||
| /// # Ownership | ||
| /// | ||
| /// The returned string borrows from `assignment`. It must not be used after `assignment` is | ||
| /// freed. | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn ddog_ffe_assignment_get_variant( | ||
| assignment: Handle<ResolutionDetails>, | ||
| ) -> *const c_uchar { | ||
| match unsafe { assignment.as_ref() } { | ||
| ResolutionDetails(Ok(assignment)) => assignment.variation_key.as_ptr(), | ||
| _ => std::ptr::null(), | ||
| } | ||
| } | ||
| 
     | 
||
| /// Get allocation key produced by evaluation. Returns `NULL` if evaluation did not produce any | ||
| /// value. | ||
| /// | ||
| /// # Ownership | ||
| /// | ||
| /// The returned string borrows from `assignment`. It must not be used after `assignment` is | ||
| /// freed. | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn ddog_ffe_assignment_get_allocation_key( | ||
| assignment: Handle<ResolutionDetails>, | ||
| ) -> *const c_uchar { | ||
| match unsafe { assignment.as_ref() } { | ||
| ResolutionDetails(Ok(assignment)) => assignment.allocation_key.as_ptr(), | ||
| _ => std::ptr::null(), | ||
| } | ||
| } | ||
| 
     | 
||
| // TODO: add accessors for various data inside ResolutionDetails. | ||
| 
     | 
||
| /// Frees an Assignment handle. | ||
| /// | ||
| /// # Safety | ||
| /// - `assignment` must be a valid Assignment handle | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn ddog_ffe_assignment_drop(assignment: *mut Handle<ResolutionDetails>) { | ||
| unsafe { Handle::free(assignment) } | ||
| } | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| 
     | 
||
| use std::ffi::{c_char, CStr}; | ||
| 
     | 
||
| use anyhow::ensure; | ||
| use function_name::named; | ||
| 
     | 
||
| use datadog_ffe::rules_based::{Configuration, UniversalFlagConfig}; | ||
| use ddcommon_ffi::{wrap_with_ffi_result, Result}; | ||
| 
     | 
||
| use crate::Handle; | ||
| 
     | 
||
| /// Creates a new Configuration from JSON bytes. | ||
| /// | ||
| /// # Ownership | ||
| /// | ||
| /// The caller must call `ddog_ffe_configuration_drop` to release resources allocated for | ||
| /// configuration. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// - `json_str` must be a valid C string. | ||
| #[no_mangle] | ||
| #[named] | ||
| pub unsafe extern "C" fn ddog_ffe_configuration_new( | ||
| json_str: *const c_char, | ||
| ) -> Result<Handle<Configuration>> { | ||
| wrap_with_ffi_result!({ | ||
| ensure!(!json_str.is_null(), "json_str must not be NULL"); | ||
| 
     | 
||
| let json_bytes = unsafe { CStr::from_ptr(json_str) }.to_bytes().to_vec(); | ||
| 
     | 
||
| let configuration = | ||
| Configuration::from_server_response(UniversalFlagConfig::from_json(json_bytes)?); | ||
| 
     | 
||
| Ok(Handle::from(configuration)) | ||
| }) | ||
| } | ||
| 
     | 
||
| /// Frees a Configuration. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// `config` must be a valid Configuration handle created by `ddog_ffe_configuration_new`. | ||
| #[no_mangle] | ||
| pub unsafe extern "C" fn ddog_ffe_configuration_drop(config: *mut Handle<Configuration>) { | ||
| unsafe { Handle::free(config) }; | ||
| } | 
Uh oh!
There was an error while loading. Please reload this page.