Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ comline-core = { git = "https://github.com/ComlineProject/core", rev = "98d64fae
# `comline-codegen`'s types, so the rev below must be byte-identical to the
# one *they* pin - Cargo only unifies a git dependency across identical rev
# strings, and two copies of `GenRequest` are two incompatible types.
comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "2d922cebb347ea1939d90c713ccf77a7ec82db65" }
comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "8bbce6dbe7f872550f4300025e5c139ff5ec304c", optional = true }
comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "be8bc79d99f3196cebfded93f08c26e0de1c72fc", optional = true }
comline-codegen = { git = "https://github.com/ComlineProject/generation", rev = "b116831182c32e70fb4452ff9fce4b463ce4bd99" }
comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "0e5c60ebdfab9cd172f23691a18084d651bfb33a", optional = true }
comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "765fc2d1177d0044498b00160000ac5084540af2", optional = true }
# Runs a `generator = "<path>.wasm"` target (see src/external.rs): no
# preopened directories, no environment, no network. Exact pins, bumped
# together.
Expand Down
44 changes: 41 additions & 3 deletions src/commands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,21 @@ fn generate_once(work_dir: &Path, overrides: &Overrides, dry_run: bool, list: bo
})
.unwrap_or_default();

// The encodings the package and its dependencies declare, each with its
// suggested mappings: what a target's `encodings` in comline.toml
// override, beside the official ones every package sees.
let declared_encodings: Vec<ConfigUnit> = config_units
.iter()
.chain(
context
.dependency_configs
.iter()
.flat_map(|(_, units)| units),
)
.filter(|u| matches!(u, ConfigUnit::Encoding { .. }))
.cloned()
.collect();

// Crate / package name for a `lib`-mode manifest — the congregation name,
// `::` flattened to `-` so it is a valid crate name.
let package_name = context.config.name.value.replace("::", "-");
Expand Down Expand Up @@ -260,7 +275,7 @@ fn generate_once(work_dir: &Path, overrides: &Overrides, dry_run: bool, list: bo
});

for gv in &versions {
let request = GenerateRequest {
let mut request = GenerateRequest {
mode,
schemas: gv.schemas.clone(),
package: PackageMeta {
Expand All @@ -281,10 +296,33 @@ fn generate_once(work_dir: &Path, overrides: &Overrides, dry_run: bool, list: bo
external_std: t.external_std,
async_api: t.async_api,
validate_on_decode: t.validate_on_decode,
// Filled from the encodings the package declares once
// `comline.toml` maps them (core#120, B3).
// Filled just below.
encodings: Default::default(),
};
// The type each encoding the package uses maps to in this
// language: comline.toml's mapping, else the declaring package's
// suggestion. One left with neither is refused here, by name.
if let Err(e) = comline_codegen::encodings::fill(
&mut request,
comline_core::encodings::official::units(),
&declared_encodings,
&t.encodings,
) {
// A built-in generator's support table is what says why it
// can't take this package (TypeScript has no MessagePack
// codec, and a tracking link for it); an encoding it has no
// mapping for is that same fact, so the table's refusal comes
// first, worded as the generator's own.
if let Source::Builtin(resolved) = &source {
let options = features::Options::of(&request);
if let Some(refusal) =
lang_targets::unsupported(&t.language, resolved, &request.schemas, &options)
{
return Err(miette!("`{}` generator: {refusal}", t.language));
}
}
return Err(miette!("target `{}`: {e}", t.language));
}
// Deliberately not `core_err`: a generator's errors are about
// schema/package *structure* (e.g. a namespace that's both a
// schema and a parent of deeper schemas), not source *text* -
Expand Down
1 change: 1 addition & 0 deletions src/commands/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const COMLINE_TOML: &str = "\
# package_versions = \"latest\" # latest | all | [\"0.3.0\", \"0.4.0\"]
# validate_on_decode = \"off\" # run `@validators` while decoding: off | collect | first
# async = false # protocols on a connection get async handlers and senders
# encodings = { \"com.acme.lines@1\" = \"acme::LinesFraming\" } # the type implementing an encoding, over its package's suggestion
#
# One block per declared language you want to configure. A block sets that
# language's output; it does not decide which languages are generated, and
Expand Down
88 changes: 88 additions & 0 deletions src/gen_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,72 @@ pub struct Generate {
/// A generator whose output is async anyway ignores it. Default `false`.
#[serde(rename = "async")]
pub async_api: Option<bool>,
/// Which type implements each encoding (format, framing, handshake) in
/// the generated code, by encoding name:
/// `encodings = { "com.acme.lines@1" = "acme::LinesFraming" }`, or with
/// the package it comes from, `{ type = "...", package = "...", version
/// = "..." }`. Overrides the declaring package's suggested mapping, and
/// maps an encoding that has none. A target's own `encodings` override
/// these, name by name.
pub encodings: EncodingMap,
/// `[[generate.target]]` blocks.
#[serde(rename = "target")]
pub targets: Vec<Target>,
}

/// `encodings = { ... }`, by encoding name as written.
pub type EncodingMap = std::collections::BTreeMap<String, EncodingEntry>;

/// One entry of `encodings`: the type alone, or with its package.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(untagged)]
pub enum EncodingEntry {
/// `"acme::LinesFraming"`.
Type(String),
/// `{ type = "LinesFraming", package = "acme-encodings", version = "^0.2" }`.
Full(EncodingTable),
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EncodingTable {
#[serde(rename = "type")]
pub ty: String,
pub package: Option<String>,
pub version: Option<String>,
}

impl EncodingEntry {
pub fn mapping(&self) -> comline_codegen::protocol::EncodingMapping {
match self {
EncodingEntry::Type(ty) => comline_codegen::protocol::EncodingMapping {
type_path: ty.clone(),
package: None,
version: None,
},
EncodingEntry::Full(t) => comline_codegen::protocol::EncodingMapping {
type_path: t.ty.clone(),
package: t.package.clone(),
version: t.version.clone(),
},
}
}
}

/// `[generate]`'s `encodings` with a target's over them, by canonical
/// name, so `jsonrpc@1` in one and `json-rpc` in the other are the same
/// entry and the target's wins.
fn merge_encodings(
section: &EncodingMap,
target: &EncodingMap,
) -> std::collections::BTreeMap<String, comline_codegen::protocol::EncodingMapping> {
let mut merged = std::collections::BTreeMap::new();
for (name, entry) in section.iter().chain(target) {
merged.insert(comline_codegen::encodings::canonical(name), entry.mapping());
}
merged
}

/// One `[[generate.target]]` block.
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
Expand Down Expand Up @@ -155,6 +216,9 @@ pub struct Target {
pub validate_on_decode: Option<String>,
#[serde(rename = "async")]
pub async_api: Option<bool>,
/// See [`Generate::encodings`]; these override those, name by name.
#[serde(default)]
pub encodings: EncodingMap,
/// `lib` mode only: write the crate straight into `out` instead of
/// `<out>/<language>/`. Target-only (no `[generate]` default, no flag/env)
/// — a shared `out` across several `lib` targets needs the per-language
Expand Down Expand Up @@ -240,6 +304,9 @@ pub struct ResolvedTarget {
pub validate_on_decode: ValidateOnDecode,
/// See [`Generate::async_api`].
pub async_api: bool,
/// [`Generate::encodings`] with the target's over them, by canonical
/// encoding name.
pub encodings: std::collections::BTreeMap<String, comline_codegen::protocol::EncodingMapping>,
}

/// Variables a `layout` template can reference.
Expand Down Expand Up @@ -393,6 +460,7 @@ pub fn resolve(
external_std: None,
validate_on_decode: None,
async_api: None,
encodings: Default::default(),
flatten: false,
}]
} else {
Expand Down Expand Up @@ -580,6 +648,7 @@ pub fn resolve(
external_std,
validate_on_decode,
async_api: t.async_api.or(cfg.generate.async_api).unwrap_or(false),
encodings: merge_encodings(&cfg.generate.encodings, &t.encodings),
});
}

Expand Down Expand Up @@ -1022,4 +1091,23 @@ mod tests {
"`validate_on_decode = \"always\"` for target `rust` - expected one of `off`, `collect`, `first`"
);
}

#[test]
fn a_targets_encodings_override_the_sections_by_canonical_name() {
let cfg: ComlineToml = toml::from_str(
"[generate]\n\
encodings = { \"jsonrpc@1\" = \"section::Rpc\", \"com.acme.cbor@1\" = \"section::Cbor\" }\n\
[[generate.target]]\n\
language = \"rust\"\n\
encodings = { \"json-rpc\" = { type = \"mine::Rpc\", package = \"mine\", version = \"1\" } }\n",
)
.unwrap();
let merged = merge_encodings(&cfg.generate.encodings, &cfg.generate.targets[0].encodings);
let keys: Vec<&str> = merged.keys().map(String::as_str).collect();
assert_eq!(keys, ["com.acme.cbor@1", "jsonrpc"]);
assert_eq!(merged["jsonrpc"].type_path, "mine::Rpc");
assert_eq!(merged["jsonrpc"].package.as_deref(), Some("mine"));
assert_eq!(merged["com.acme.cbor@1"].type_path, "section::Cbor");
assert_eq!(merged["com.acme.cbor@1"].package, None);
}
}
Loading
Loading